所有方法
Object.defineProperty()Object.definePropertise()Object.getOwnPropertyDescriptor()Object.getPrototypeOf()instanceofisPrototypeOf()hasOwnProperty()infor-inObject.keys()Object.getOwnPropertyNames()Object.create()
理解对象
属性类型
ES有两种属性:数据属性/访问器属性
默认对象字面量设置的属性都是数据属性,访问器属性需要defineProperty()/definePropertise()方法设置
数据属性:包含一个数据值的位置,此位置可以读取和写入
定义行为的特征值有以下:[[Configurable]] 是否能通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否修改为访问器属性。开发人员直接在对象定义的属性该特性默认值是true[[Enumerable]] 能否通过for-in返回。…默认值是true[[Writable]] 能否修改属性的值。…默认值是true[[Value]] 包含属性的数据值,读取和修改就在此位置。…默认值是undefined
修改属性特性方法: Object.defineProperty(),参数一对象,参数二属性名,参数三特性对象(可设置一或多个值)
⚠️IE9+
⚠️特性对象中不指定的特性默认是false/undefined

访问器属性:包含一对getter()/setter()函数,不包含数据值
定义行为的特征值有以下:[[Configurable]] 是否能通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否修改为数据属性。开发人员直接在对象定义的属性该特性默认值是true[[Enumerable]] 能否通过for-in返回。…默认值是true[[get]] 读取属性时调用的函数,默认值undefined[[set]] 写入属性时调用的函数,默认值undefined
利用Object.defineProperty()设置特性,不设置get不能读,不设置set不能写:

一次性定义多个属性:Object.definePropertise() 参数一对象,参数二(属性:特性对象)格式的对象
⚠️IE9+
⚠️可以针对任何对象包括DOM/BOM使用此方法

读取属性特性:Object.getOwnPropertyDescriptor() 参数一对象,参数二属性名,返回特性对象,仅是实例的非原型的属性描述,想返回原型上的属性描述则直接在原型上调用
⚠️IE9+

创建对象
Object构造函数或者对象字面量创建单个对象缺点:同一个接口创建很多对象,产生大量的重复代码
工厂模式
1 | function createPerson(name,age){ |

解决:封装函数,创建多个相似对象
问题:对象识别问题-怎样知道对象类型
构造函数模式
1 | function CreatePerson(name,age){ |

⚠️构造函数规定大写字母开头,为了区别于其他函数,构造函数本身也是函数,只不过是用途于创建对象
⚠️new调用构造函数过程:
1.创建一个新对象
2.构造函数作用域指向新对象
3.执行构造函数中代码
4.返回新对象
⚠️不存在构造函数特殊语法,通过new调用就是构造函数,不通过new调用就是普通函数
解决:实例标识为一种特定类型,能够instanceof判断具体对象类型
问题:每个实例上都实例化一遍函数对象,创建多个同样功能的函数没有必要,也不必要耦合函数方法和对象(在构造函数外即全局作用域下写函数方法,然后在构造函数中定义新对象方法时指向全局函数,并不是好方法,虽然解决了共享同一个方法,但是全局函数仅被某个对象使用导致全局作用域名副其实、同时会污染全局环境、也失去了自定义的引用类型的封装性)
原型模式
理解原型
过程:
创建一个新函数,根据一组特定规则为该函数创建属性prototype,属性是指向原型对象的指针
原型对象自动获取constructor(构造函数)属性,是指向新函数的指针,其他方法均继承自Object
创建新实例,实例将包含[[prototype]]内部属性指针,指向原型,使其与原型关联(并非与构造函数关联)
1 | function Person(){} |


Object.getPrototypeOf() 获取实例的原型,IE9+
读取对象属性过程:
搜索实例本身 => 搜索原型对象
⚠️原型最初只包含constructor,这个属性也是被实例共享的,所以实例可以person1.constructor访问到构造函数,即进行了两次搜索在原型中找到该属性
⚠️可以通过实例访问到原型中的值,但不能通过实例更改原型中的值,即实例属性会屏蔽原型中的属性访问(这也是上面提到的访问属性过程中,第一次搜索实例本身就有则不再继续搜索)

各种获取属性方法
in操作符: for-in循环/单独使用
-单独使用: 属性在对象中返回true,包括原型中存在
⚠️可以通过hasOwnProperty()方法和in操作符可以确认属性存在实例中还是原型中1
2
3function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name) && name in object
}

-for-in循环: 返回所有可枚举属性数组,包括原型中属性
其他相关方法:Object.keys()返回实例的所有可枚举属性;Object.getOwnPropertyNames返回实例中所有属性数组(无论是否可枚举)
更简单的原型语法/原型动态性
1 | function Person(){} |
⚠️上述写法其实是重写了Person原型,新原型是Object字面量对象实例,此实例自动获取的constructor(其实是实例的原型自动获取的属性)指向Object并非Person,但并不影响instanceof返回值,如果constructor很重要可以重写原型时重新定义(暂时不知道有何用处),但是字面量定义会导致其可枚举,可以通过defineProperty方法定义不可枚举属性
⚠️在原型中查找值是一次性的搜索,所以先创建实例,后改动原型也会在实例中反应,但是重写原型会导致原创建实例与新原型无关联,原创建实例的[[prototype]]依然指向的是原来自动生成的原型,注意重写原型要在创建实例之前

⚠️原生引用类型也是采用原型模式创建的,都在构造函数的原型上定义了方法,但是不推荐修改原生对象原型!!!
解决:所有实例通过原型共享属性和方法
问题:由于原型属性被共享的特性,对于引用类型比如数组,一个实例push修改了数组,另个实例上也能反应出来(重新在实例上创建属性不存在这种问题,因为可以屏蔽原型属性)
组合使用构造函数模式和原型模式
构造函数用于定义实例属性、原型模式用于定义方法和共享的属性,最大限度节省内存
还可以向构造函数传递参数
是使用最广泛、认同度最高的一种创建自定义类型的方法-默认模式
1 | function Person(name, age){ |

动态原型模式
所有信息都放在构造函数中,仅在必要情况下,才在构造函数中初始化原型,同时保留了构造函数和原型的优点1
2
3
4
5
6
7
8
9
10
11function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
if(typeof this.sayName != 'function'){
Person.prototype.sayName = function(){
console.log(this.name);
};
}
}

寄生构造函数模式
1 | <!-- 除了要使用new操作符并叫其构造函数外,和工厂模式没有差别 --> |
一般用来应对特殊情况,比如生成包含额外方法的数组
⚠️能使用其他模式的情况下不要使用这种模式,不能通过instanceof确定对象类型
稳妥构造函数模式
稳妥对象:没有公共属性、不引用this。适合在安全环境或者防止数据被其他应用程序改动时使用1
2
3
4
5
6
7
8
9
10
11
12
13
14function Person(name,age){
var o = new Object();
<!-- 可以在此定义私有变量和函数 -->
<!-- 和寄生模式相比差距一添加实例方法不用this -->
o.sayName = function(){
console.log(name);
};
return o;
}
<!-- 差距二不使用new调用构造函数 -->
var person1 = Person('xiaohei',25)
⚠️这种方式,除了使用实例的sayName方法外没有别的途径能访问name属性、即便能为对象添加其他属性,也不能更改原始属性数据
以上其实是闭包原理
继承
oo语言支持两种继承方式:接口继承-继承方法的签名;实现继承-继承实际的方法
ES只支持实现继承,主要依靠原型链实现
原型链
基本模式
引用类型A继承引用类型B,重写A的原型是B的实例,作为B的实例,A的原型指向B的原型,如此可以层层递进,构成了实例与原型的链条
基本模式:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
<!-- 重写原型实现继承 -->
SubType.prototype = new SuperType();
<!-- 继承的基础上添加了自己的方法 -->
SubType.prototype.getSubValue = function(){
return this.subproperty;
}
var instance = new SubType();

⚠️重写的原型没有constructor指向SubType构造函数,而作为SuperType的实例能访问到SuperType原型的constructor指向SuperType构造函数
⚠️重写的原型作为SuperType的实例,存在[[prototype]]内部属性指向SuperType原型
⚠️所有引用类型都继承Object,也是通过原型链实现的,所以SuperType原型是作为Object的实例存在的,存在[[prototype]]内部属性指向Object原型
⚠️继承的基础上添加自己的方法一定要在重写原型之后,添加的方式不能是字面量方式,那是又一次重写原型了!!!
确定原型与实例关系
方法一: instanceof
通过与原型链中出现的构造函数关系确定
方法二:isPrototypeOf
通过与原型链中出现的原型关系确定
原型链问题
问题一:引用类型副作用
问题二:无法在不影响所有对象实例的情况下,向超类型构造函数传递参数
借用构造函数(经典继承/伪造对象)
在子类型构造函数中调用超类型构造函数(利用call或apply当作普通函数调用)
基本模式
1 | function SuperType(name){ |
问题
依然是构造函数模式的问题,函数复用无从谈起
组合继承(伪经典继承)
原型链实现对原型属性和方法的继承,借用构造函数实现对实例属性的继承,保证了既有函数复用又有实例自己的属性
最常用的继承模式
基本模式
1 | function SuperType(name){ |
原型式继承
借助原型,基于已有对象创建新对象,同时不必创建自定义类型
基本模式
1 | <!-- 称之为对o进行了一次浅复制 --> |
⚠️通过这种方式传入相同的对象而创建的对象实例原型指向同一个,共享原型方法和属性,所以就像原型模式一样存在引用类型副作用
Object.create
ES5新增Object.create()方法规范了原型式继承,参数一:原型对象,参数二(可选):新对象实例额外属性的对象(同Object.definePropertise()第二个参数形式一样)
IE9+
⚠️在不需要兴师动众创建构造函数,只想让一个对象与另一个对象保持类似的情况下,可以胜任
寄生式继承
与原型模式紧密相关的一种思路
和寄生构造函数和工厂模式类似,仅用于封装继承过程,增强对象
基本模式
1 | function createAnother(original){ |
⚠️这种方式增强对象方法,与构造函数函数模式类似的点是函数不会得到复用
⚠️主要考虑对象,而不是自定义类型和构造函数的情况下,此方式是有用的模式
寄生组合式继承
最理想的继承范式
此方法为解决组合继承的问题(调用两次超类型构造函数,第一次重写子类型原型时使其作为超类型的实例具有超类型的属性,第二次创建子类型实例时又使实例重写了原型属性)
使用借用构造函数继承属性,原型链的混成模式继承方法,基本思路是重写子类型原型时不调用超类型构造函数,实际需要的是超类型原型的副本,可以使用寄生式继承来继承超类型原型,然后将结果指定给子类型的原型
基本模式
1 | function inheritPrototype(subType,superType){ |
利用上面方法替换调用超类型构造函数重写子类型原型即可
⚠️此方法的高效在于只调用一次超类型构造函数,避免在子类型原型上创建不必要的、多余的属性