本文主要是对 【小红书】 中原型和继承进行了归纳总结。
原型模式
- 创建的每一个函数都包含一个
prototype
(原型)属性,这个属性是一个指针,指向 原型对象 (即平时所说的 “原型”); - 所有的原型对象都会自动获得一个
constructor
(构造函数)属性,这个属性也是一个指针,指向一个包含prototype
属性所在的函数; - 调用构造函数创建实例,每个实例都包含一个
[[prototype]]
(_proto_)属性(内部属性),这个属性还是一个指针,指向构造函数的原型对象。
解释一下 \_proto\_
:在脚本中没有标准的方式访问 [[prototype]],但 Firefox、Safari 和 Chrome 在每个对象上都支持一个属性(\_proto\_
),它就相当于是对象实例与构造函数的原型对象之间的一个连接。
(对照着下图进行理解)
原型对象的用途: 包含可以由特定类型的所有实例共享的属性和方法;
使用原型对象的好处: 让所有对象实例共享它所包含的属性和方法,不必在构造函数中定义对象实例的信息,而是将这些信息直接添加到原型对象中。
下面,直接上代码和图,说明一下各个对象之间的关系。
|
|
上面代码中,首先创建了一个名为 Person
的构造函数。由图可以看出,Person
函数有一个 prototype
属性,它是一个指针,指向该函数的原型对象(Person Prototype
);紧接着,给原型对象添加属性和方法,这些属性和方法可以由对象实例共享;最后,使用构造函数来创建对象实例,并调用方法。这里创建了两个对象实例:person1
和 person2
,他们拥有相同的属性和方法,因此最后一句代码弹出的是 true
。
原型对象
对象实例与原型对象的关系
isPrototypeOf(): 判断对象实例的 [[prototype]] 内部属性是否指向原型对象;
1alert(Person.prototype.isPrototypeOf(person1)); // trueObject.getPrototypeOf(): 该方法返回 [[prototype]] 的值;
1alert(Object.getPrototypeOf(person1)); // Person.prototype
实例对象访问属性
每当代码读取某个实例对象的属性时,都会按一下步骤来执行:
- 首先搜索实例对象本身是否具有给定名字的属性,如果有则返回该属性的值;
- 如果没有,则在原型对象中进行搜索,找到了则返回该属性的值。
原型对象最初只包含 constructor
属性,该属性也是共享的,对象实例可以访问到。
虽然可以通过对象实例访问保存在原型对象中的值,但却不能通过对象实例重写原型对象中的值。(如果实例对象中有一个属性与原型对象中的属性同名,则根据访问顺序,原型对象中的同名属性会被屏蔽;可以通过 delete
删除实例属性,让原型对象中的同名属性重见天日。)
hasOwnProperty()
用来判断某个属性是来源于实例对象本身,还是来源于原型对象
(只要当该对象来源于实例对象本身的时候,该方法才会返回 true
)
原型对象与 in 操作符
单独使用
如果通过实例对象能够访问到给定的属性(无论来源于哪里),都会返回true
。123456789function Person(){}Person.prototype.name = "Bob";Person.prototype.age = 29;var person1 = new Person();person1.name = "Alice";alert("name" in person1); // truealert("age" in person1); // true在
for-in
循环中使用
返回的是所有能够通过实例对象访问的、可枚举的属性(无论该属性来源于哪里)。
注意:如果某个属性在原型对象中是不可枚举的(即该属性的 [[Enumerable]]
被设置为 false
),实例对象中有一个同名属性,则该实例属性还是会在 for-in
循环中返回的。(所有开发人员定义的属性都是可枚举的,只有在IE8及更早版本中例外)
可替代方法:Object.keys()
和 Object.getOwnPropertyNames()
原型语法的简化
|
|
上面代码中,不需要在每次添加属性和方法的时候都敲一遍 Person.prototype
,而是将 Person.prototype
设置为等于一个以对象字面量形式创建的新对象。
注意:这里 constructor
属性不再指向 Person
构造函数,因为这里完全重写了 Person.prototype
对象。
原型的动态性
- 可以随时为原型对象添加属性和方法,并且修改能够立即在所有对象实例中反应出来;
- 如果重写整个原型对象,实例对象中的
[[prototype]]
内部属性指向的还是最初的那个原型对象,而不是这个新的原型对象;
原型对象的问题
- 所有实例在默认情况下会取得相同的属性值,在某种程度上会带来一些不方便;
- 由其共享的本性所导致 —— 特别是包含引用类型值的属性的时候;123456789101112131415function Person(){}Person.prototype = {constructor: Person,name: "Bob",age: 29,friends: ["Alice","Lucy"]}var person1 = new Person();var person2 = new Person();person1.friends.push("Cindy");alert(person1.friends); // "Alice, Lucy, Cindy"alert(person2.friends); // "Alice, Lucy, Cindy"alert(person1.friends == person2.friends); // true
上面代码中,Person.prototype
原型对象中的 friends
属性是一个数组,而 person1
和 person2
这两个实例对象访问属性 friends
时,其实指向的是同一个数组,所以对 person1.friends
的修改也会在 person2.friends
上反映出来。
但是……
如果是在 person1
实例对象中重写 friends
属性就不一样了。然而如果想在原先属性的基础上做修改,这种方法还是解决不了问题。
因此,基于以上问题,原型模式很少单独使用。
对原型模式的改进
组合使用构造函数模式和原型模式
—— 使用最广泛、认同度最高
- 构造函数模式: 定义实例属性;
- 原型模式: 定义方法和共享的属性;
|
|
动态原型模式
- 把所有信息都封装在构造函数中,通过在构造函数中初始化原型对象(仅在必要的情况下),保持了同时使用构造函数和原型的优点。
|
|
以上代码中,只有在 sayName()
方法不存在的情况下,才会将 sayName()
方法和 job
属性添加到原型中。if
语句部分,只会在初次调用构造函数时才执行, 之后原型对象已经初始化,不需要再做什么修改。if
语句检查的可以是初始化之后应该存在的任何属性和方法,如果不止一个,也不需要每一个属性和方法都检查一遍,只要检查其中一个即可。(上面代码就只检查了一个)