一、创建对象的方法:
1、使用Object简单创建
2、使用对象字面量
3、以上方法1和2会产生大量的重复代码,因此我们引入了“工厂模式”!
分析:可以传入参数
解决了多个相似对象的问题
没有解决对象识别的问题!
4、“构造函数模式”
分析一下new的过程发生了什么?
创建一个新对象
将构造函数的作用于赋给这个新对象(即this指向新对象)
执行构造函数(为新对象添加属性和方法)
返回新对象
解决“工厂模式”存在的对象识别问题:
(1)实例(person1、person2)的constructor属性指向Person对象。
(2)实例 instanceof 对象(返回布尔值)
使用“构造函数模式”的缺点:
每个方法都要在每个实例上创建一遍(明明是可以公用的)
初级解决方案:将这些方法放在全局作用域中定义,然后在构造函数内部引用(但是没有封装性可言了)
5、“原型模式”
函数(包括构造函数)
函数的prototype属性:函数的原型对象
原型对象的constructor属性(也是共享的,可以由实例读出):构造函数(例如Person)
实例的_proto_属性:原型对象
判断方法:确定原型和实例之间的关系
(1)Person.prototype.isPrototypeOf(person1)
(2)ES5中 Object.getPrototypeOf(person1) == Person.prototype
注意:实例中的同名属性仅仅在该实例中有效,它不会改变原型对象中的属性值!
那么我们怎么判断一个属性是在实例中还是在原型对象中呢?
某实例.hasOwnProperty(属性名)
返回true:实例中
返回false:原型对象中
判断属性是否存在(无论在实例中还是原型对象都可以)
(1)属性名 in 对象名
返回true:存在
返回false:不存在
(2)for-in的理解:只能读到可枚举的属性,
实例中的同名属性(哪怕在原型中该属性是不可枚举的)也能读到-->但是IE早期版本的bug不会读取这种情况
在这里注意一下,ES5将constructor和prototype属性都设置为false,虽然各种浏览器的实现并不完全遵循。
读取可枚举属性:
ES5中的Object.keys(原型对象):原型对象的所有可枚举属性
ES5中的Object.keys(实例对象):实例对象的属性,不包括继承的属性
读取所有属性(无论可枚举或者不可枚举):
Object.getOwnPropertyNames()
上面的“原型模式”中定义每个属性、方法都要敲一遍Person.prototype,为了方便,也可以直接将包含多个属性、方法的对象字面量直接赋给Person.prototype,如图所示:
这种情况下需要注意:
原本在新建一个函数的时候会默认给原型对象一个constructor属性,指向Person,但是现在对象字面量重写了整个Person.prototype,constructor属性也就没有了,这时候需要手动设置constructor属性为Person,因为手动设置constructor属性变成可枚举属性(虽然instanceof Person还是true,但是constructor属性已经不是Person了,是Object)
说了这么多,是时候谈谈“原型模式”的优缺点了!
优点:解决了“构造函数模式”中的共享方法的问题
缺点:
(1)构造函数为空,不能传入参数,所有实例在默认情况下属性都一样
(2)属性值为引用类型(例如数组时),实例中修改该属性,原型对象中的属性也会随之改变,因为都是指向同一个对象
6、组合使用“构造函数模式”和“原型模式”----使用最广泛的方式
扬长避短!!
构造函数模式:定义实例属性,支持传参
原型模式:定义共享属性和方法
二、属性的特性:
1、数据属性
[[Configurable]]:true的时候表示可以通过delete删除属性或者能把属性特性修改为访问器属性(直接在对象上定义的属性默认为true)
注意:一旦修改为false以后不能再进行配置,会抛出错误!!!!
[[Enumerable]]:true的时候表示可枚举(直接在对象上定义的属性默认为true)
[[Writable]]:true的时候表示可以修改属性(直接在对象上定义的属性默认为true)
[[Value]]:属性的数据值(默认为undefined)
注意:调用ES5中的Object.defineProperty(对象,待修改属性,属性特性组)方法可以修改属性的特性,其中CEW(简写)这三个默认为false!!!!
2、访问器属性
[[Configurable]]同上
[[Enumerable]]同上
[[Get]]:读取属性时调用的函数,默认为undefined
[[Set]]:写入属性时调用的函数,默认为undefined
注意:访问器属性和数据属性不同,只能通过调用ES5中的Object.defineProperty(对象,待修改属性,属性特性组)方法来设置属性的特性!!!!
同时定义多个属性可以使用Object.defineProperties(对象,待修改属性组)方法。
3、读取属性的特性:
ES5中的Object.getOwnPropertyDescriptor(对象, 属性名)
三、继承(主要是依靠原型链)
1、通过原型链实现继承(本质是重写原型对象为实例)
上图的例子是B继承了A
注意:
实例 instanceof 所有原型链上出现的构造函数(都返回true)
原型对象 isPrototypeOf 实例 (所有原型链上出现的都返回true)
这种实现继承的方法的缺陷:
跟创建对象使用原型模式一样,存在引用类型的属性值问题
2、借用构造函数来实现继承(思想是在子类型构造函数内部调用超类型构造函数)
优点:
解决了原型链模式中的引用类型值的问题,每个实例都有自己的副本
缺点:
(1)函数复用问题,跟创建对象时使用构造函数方法一样
(2)超类型的原型中的方法,子类型不可见(原型链方式继承的话可以)
3、组合继承
和使用组合方式创建新对象的思路一样,使用原型链继承公共属性和方法,使用借助构造函数的方式继承实例的属性。
4、原型式继承(思想:对某一个对象进行浅复制)
ES5中提出了Object.create(需要被复制的对象,新对象新增的属性)
缺陷:和原型链继承一样,存在引用类型属性值的问题
5、寄生式继承
在原型式继承的基础上进行进一步封装,定义新对象自己的方法,然后返回新对象
缺陷:跟借助构造函数一样,不能达到函数复用
6、寄生组合式继承----比起组合式继承更高效率
上面提到的常用的组合继承方式中,存在的不足是每次都要调用两次超类型构造函数
(1)子类型构造函数中调用,获取实例属性
(2)修改子类型原型时调用,获取公共属性和方法
寄生组合式继承的思路:不必为了修改子类型的原型对象而调用超类型构造函数(即(2)),得到一份超类型原型的副本就可以了