前言
今天从家里回到了学校,在家呆了十天,胖了几斤的重量,又折腾回学校了,春节回家真是艰辛的路途。随便扯扯我的往返行程:为了省钱我没有选择直飞到长春往返都是到北京转的,这样我和女朋友可以节省4000块左右。1月24号深圳-飞机-北京(飞机晚点1个小时),到北京已经凌晨两点多,机场大巴就剩两个了,做一个大巴到了市里在打的到同学那100块没了,到同学那(天通苑附近)都三点了,吃饭喝酒(一瓶牛栏山+2瓶啤酒)到了五点,睡觉到十点,起床去打了两个小时的篮球,回来没吃饭地铁去车站,差一点没有赶上动车去长春,晚上九点多到了长春,打车去长春理工同学那住,晚上要了肯德基外卖,100块又没了,十二点睡觉三点起床赶车回松原下乡参加同学婚礼,下午1点回来做汽车回家。哎,折腾死人了,放假在家天天叔叔大爷家轮流吃饭,天天打麻将......2月6号四点半起床赶车去市里做火车去长春为了赶上10点25分的车去北京,九点左右到了北京,坐机场块钱去首都机场,坐飞机直飞香港,到香港凌晨3点多,出战坐大巴去深圳湾回深圳,到宿舍8点多。感觉一直都是在赶车,好累,有钱就直飞,哼。
就扯到这,今天和大伙一起学习javascript中的对象和创建对象的一些方式。
对象
javascript中什么是对象呢,javascript中一切都是对象,任何对象的都是基于Object的,确切的说每一个对象都是基于一个引用类型创建的,这个引用类型可以是原生的引用类型也可以是我们自己创建的,这些引用类型是基于Object的。最简单的创建对象的方法是
var person = new Object();person.name = "hainan";person.age = "25";
直接new出来一个Object对象,之后给这个对象添加属性和方法。
属性类型
在第五版的javascript中,定义了在内部使用的特性,描述了属性的各种特征,这些特性是javascript引擎使用的,在javascript中我们不能使用它们,为了区别将他们放在了两对方括号中,属性类型分为两种:数据属性和访问器属性。
数据属性
- [[Configurable]] 表示能否通过delete删除属性,默认为true
- [[Enumerable]] 表示能否通过for-in枚举属性,默认为true
- [[Writable]] 表示能否修改属性的值,默认为true
- [[Value]] 表示这个属性的值,默认为undefined
访问器属性
- [[Configurable]] 表示能否通过delete删除属性,默认为true
- [[Enumerable]] 表示能否通过for-in枚举属性,默认为true
- [[Get]] 读取属性是调用的函数,默认值为undefined
- [[Set]] 写入属性时调用的函数,默认值为undefined
ECMAScript5中的属性修改时通过Object.defineProperty()这个方法,三个参数分别为对象,属性名,描述符对象。直接看例子,value特性
var person = {};Object.defineProperty(person,"name",{configurable:true,value:"hainan"})console.log(person.name);//hainan//等价于var person = {name : "hainan"}
再看看writable
var person = {};Object.defineProperty(person,"name",{configurable:true,value:"hainan",writable:false});person.name = "allenxing";//修改属性console.log(person.name);//hainan 严格模式下出现错误
下面看看configuration特性
var person = {};Object.defineProperty(person,"name",{configurable:false,value:"hainan"});delete person.name;//删除属性console.log(person.name);//hainan 严格模式下出现错误
configuration属性修改成不可以配置的,就是指为false时,就不能再把它修改成可配置的了,看看例子
var person = {};Object.defineProperty(person,"name",{configurable:false,value:"hainan"});Object.defineProperty(person,"name",{configurable:true,value:"hainan"});//TypeError: Cannot redefine property: name
enumerable特性
var person = {};Object.defineProperty(person,"name",{configurable:true,value:"hainan"});for(var pro in person){ console.log(pro);}//不输出//在chrome下默认的enumeration默认为false
var person = {};Object.defineProperty(person,"name",{configurable:true,value:"hainan",enumerable:true});for(var key in person){console.log(key)}//"name"
再看一下访问器属性
var person = { name : "hainan", age : 25 }Object.defineProperty(person,"year",{ get : function(){ return this.age + 1; }, set :function(value){ this.age = value + 10; } });console.log(person.year);//26person.year= 10;console.log(person.age);//20
ECMAScript5还有一个方法Object.defineProperties()可以定义多个属性,用法如下
var person = {}Object.defineProperties(person,{ name : {value : "hainan"}, age : {value : 25}, year : { get :function(){ return this.age + 1; }, set : function(value){ this.age = value +10; } } });console.log(person.year);//26person.year = 10;console.log(person.age);//25 注意这里
这里我也不知道为啥age的值没有改变,但是下面这样就可以改变了
var person = {name :"hainan",age:25}Object.defineProperties(person,{ year : { get :function(){ return this.age + 1; }, set : function(value){ this.age = value + 10; } } });console.log(person.year);//26person.year = 10;console.log(person.age);//20 注意这里
求大神指导下,谢谢
多写赤脚非大仙的帮助,如果没有指定描述符各属性值,默认是false或者undefined
var person = {}Object.defineProperties(person,{ name : {value : "hainan"}, age : {value : 25,writable : true}, year : { get :function(){ return this.age + 1; }, set : function(value){ this.age = value +10; } } });console.log(person.year);//26person.year = 10;console.log(person.age);//25 注意这里
其实上面这些修改属性的特性一般的时候我们是用不到的,理解这些可以帮助我们更好的了解javascript的对象。
创建对象
直接new
创建对象有许多种方法,最简单的就是直接new 一个Object对象,之后再添加属性了
var person = new Object();person.name = "hainan";person.age = 25;
对象字面量
对象字面量是比较常用的创建对象的方法,其实和new差不多,但是写起来比较简洁
var person = { name : "hainan", age : 25 }
缺点:看起来比较简单,但是假如我们要创建多个对象呢,例如person1,person2.....,直接new和对象字面量都得必须重写代码,也就是会有大量的重复代码。
工厂模式
工厂模式就是解决了代码的不可复用的问题,工厂模式用一个函数封装了创建对象的过程,看代码
function personFactory(name,age){ var obj = new Object(); obj.name = name; obj.age = age; return obj;}var person1 = personFactory("hainan",25);var person2 = personFactory("allenxing",26);
这样我们需要创建一个person对象时只需要使用personFactory()就可以了,解决了创建多个对象的复杂代码问题。其实可以看出本质还是直接new出来一个Object对象,只不过是用函数封装了起来,用一个return返回这个Object对象。
缺点:我们不知道这个对象是具体属于哪个类型的实例,也就是它没有解决对象的识别问题,当然知道对象是Object的实例没有任何实质性意义。
构造函数模式
构造函数可以创建特定类型的对象, Object和Array等是原生的构造函数,是javascript运行时就存在执行环境当中的,不需要我们自己定义构造函数,当我们需要自定义对象时我们可以自定义构造函数,
function Person(name,age){ this.name = name; this.age = age;}var person1 = new Person("hainan",25);var person2 = new Person("allenxing",26);
这里我们看到了函数的首字母是大写的,这是一种惯例,为了和其它的函数有区别。要想创建Person的实例,必须使用new操作符,看看new都干些什么了
- 创建一个新的对象
- 将构造函数的作用域赋值给这个对象,也就是this指向了这个新对象
- 执行构造函数中的代码
- 返回这个新对象
person1和person2是Person对象的不同实例,它们有一个相同的constructor属性,并且它们都是Person的实例
console.log(person1.constructor == Person);//trueconsole.log(person2.constructor == Person);//trueconsole.log(person1 instanceof Person);//trueconsole.log(person2 instanceof Person);//true
构造函数模式解决了对象识别的问题。
我们知道构造函数也是函数,如果不使用new关键字,那么会是怎样的呢,我们知道在全局作用域中this指向的是window,那么直接使用构造函数应该就是将属性或方法添加到了window上了,我们看看是不是这样的情况
function Person(name,age){ this.name = name; this.age = age;}var person = Person("hainan",25);//添加到了window上console.log(window.name);//hainan
没有new实例时,构造函数就普通函数,添加到了this指向的对象中了,在这里this指向的是window。
缺点:每当new一个实例出来,每个实例都要重新创建对象的函数,每个实例中的函数是不同的,因为函数也是对象,定义一个函数也就是实例化一个对象。
function Person(name,age){ this.name = name; this.age = age; this.getName = function(){ return this.name};//等于this.getName = new Function("return this.name");}var person1 = new Person("hainan",25);var person2 = new Person("allenxing",26);console.log(person1.getName == person2.getName);//false
虽然这个函数是所以实例共享的,但是每个实例中都是一个副本,显然这对性能是不利的。当然我们可以这样干
function Person(name,age){ this.name = name; this.age = age; this.getName = getName;}function getName(){ return this.name;}var person1 = new Person("hainan",25);var person2 = new Person("allenxing",26);console.log(person1.getName == person2.getName);//true
这样就是每个实例都引用了同一个函数,所以是相等的,但是如果方法很多,我们不能把所有的函数就定义在全局作用域当中,这就需要其他的模式来解决了。
原型模式
我们之前已经讨论过了,这里我们直接学习这个原型模式就好了。
function Person(){}Person.prototype = { constructor : Person, name : "hainan", getName : function(){ return this.name} }var person1 = new Person();var person2 = new Person();console.log(person1.getName == person2.getName);//true
这样,原型模式解决了构造函数中的每个实例都有一个方法的副本的性能和内存问题,但是原型模式也有自己的不足
缺点:原型中的属性是共享的,这是他的优点也是他的缺点,当属性是引用类型的时候,这个问题就体现出来了。
function Person(){}Person.prototype = { constructor : Person, name : "hainan", friends : ["James"], getName : function(){ return this.name} }var person1 = new Person();person1.friends.push("Melo");var person2 = new Person();console.log(person2.friends);//"James", "Melo"
可以看到我们给person1添加一个朋友,结果person2的朋友也添加上了,就是由于所有实例共享原型中的属性。
组合模式(构造函数和原型模式)
这种是最常用的方式也是大家推荐的方式,构造函数用于定义实例属性,原型模式用于定于方法和共享的属性,这样就会结合两种模式的优点,每个实例都会有一个实力属性的副本,同时共享着对方法的引用,节省了内存。
function Person(name,age){ this.name = name; this.age = age; this.friends = ["James"];}Person.prototype = { constructor : Person, getName : function(){ return this.name} }var person1 = new Person("hainan",25);person1.friends.push("Melo");console.log(person2.friends);//"James", "Melo"var person2 = new Person("allenxing",26);console.log(person2.friends);//"James"
动态原型模式
所谓的动态原型模式就是将原型模式和构造模式放在了构造函数中,在定义方法时先检查是否存在这个方法,如果存在就什么就不做,若果不存在就在原型上添加这个方法。
function Person(name,age){ this.name = name; this.age = age; if(typeof this.getName != "function"){ Person.prototype.getName = function(){ return this.name; } }}var person1 = new Person("hainan",25);var person2 = new Person("allenxing",26);
寄生构造函数模式
书上说前面的几种不合适的话就可以使用这种模式,这个模式和工厂模式其实很类似,都是将创建对象的过程封装到一个函数内部,只不过这种模式最后使用了new操作符,new操作符我们以后在详细说说,这里就不说了。
function Person(name,age){ var obj = new Object(); obj.name = name; obj.age = age; return obj;}var person1 = new Person("hainan",25);var person2 = new Person("allenxing",26);
这里是使用了带返回值的构造函数,带的返回值会覆盖构造函数默认的返回值,这个我会写一个关于这个的文章。
稳妥构造函数模式
书上说的稳妥对象就是没有公共属性,其方法也不引用this对象,看看例子,这个大家还是看看《高级程序设计》上的吧,和前面的几种对比一下就知道为啥叫稳妥了,
function Person(name,age){ var obj = new Object(); obj.getName = function(){ return name;//没有使用this }; return obj;}var person1 = Person("hainan",25);//没有使用newconsole.log(person1.getName());
小结
这篇就这样吧,开学了,还是得先紧起来,好好写博客,抓紧搞论文。大家对这篇有什么疑问尽量说,对我这篇有什么意见尽量提,小弟谢谢大家了。下篇说说继承。