原型与原型链
原型与原型链
首先,可以肯定的是,原型,是一个对象(这是一句废话)。
一、原型与构造函数
《JavaScript高级程序设计》是这样说的:
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype 属性,这个属性指向函数的原型对象。默认情况下,所有原型对象自动获得一个 constructor(构造函数)属性,这属性是一个指向 prototype 属性所在函数的指针(构造函数.prototype.constructor = 构造函数)。
当然,这个描述是有一点问题的,因为这本书描述的 js 是ES5版本及以下的,没有包括 ES6。
ES6 里的箭头函数和使用对象字面量创建对象时的简写方法,都没有 prototype 属性。
let obj = {
fun1() {},
fun2: ()=>{},
fun3: function() {}
};
console.log(
obj.fun1.prototype, // undefined
obj.fun2.prototype, // undefined
obj.fun3.prototype // {constructor: f}
);
所以,prototype 这个属性,可以理解为是针对构造函数的。虽然上例的 obj.fun1 依旧能够作为一个构造函数。
二、原型与实例
既然有原型和构造函数有关系,那么原型自然和构造函数的实例也有关系。
类似的,用构造函数的创造出的实例,也有一个指针指向原型,这个指针是一个内部属性 [[Prototype]],正常说我们是访问不到的,但是浏览器都把它具现了出来,这个指针就是__proto__,ES6 也把它写入了标准,但是不推荐使用。
现在就是:
let obj = new Object();
console.log(obj.__proto__ == Object.prototype)
那么原型有什么用呢?《JavaScript高级程序设计》是这样说的:
每当代码读取某个对象的某个属性时,都会指向一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值。如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性中的值。
说白了就是,自己没有的属性,看看原型有没有。
PS: 不是所有对象都有原型,比如 Object.create(null) 创造出来的就是一个纯粹的空对象,没有原型没有
__proto__指针。还有,Object.prototype 也是一个没有原型的对象,或者说浏览器将它的
__proto__指针设为访问器属性进行了拦截,一访问就为 null,可以视作它没有原型。
拓展:Function 与 Object 的关系
- Function 作为一个实例,是 Function 的实例。所以
Function.__proto__ == Function.prototype - Object 作为一个实例,是 Function 的实例。所以
Object.__proto__ == Function.prototype - Function 作为一个构造函数,它的原型是 Object 的实例。所以
Function.prototype.__proto__ == Object.prototype
分析以下代码:
// Function 作为一个实例,是 Fuction 的实例
console.log (Function.__proto__ == Function.prototype) // true
// Object 作为一个实例,是 Fcuntion 的实例
console.log (Object.__proto__ == Function.prototype) // true
// Fucntion 作为一个构造函数,它的原型是 Object 的实例
console.log (Function.prototype.__proto__ == Object.prototype) // true
三、原型链
如果,A 的原型是 B 的实例,那么 A.原型.原型 = B.原型,一个链关系就出来了。
根据原型的作用,当访问 A 上的一个属性,没找到,就去 A 的原型上找,在 A 的原型找不到,又因为 A 的原型还有原型,所以可以继续去 A 的原型的原型上找。
所以,原型链就成了一个类似作用域链不断往上寻找标识符的东西了。
注意:因为所有的对象都是 Object 的实例,所以原型链的尽头就是 Object 的原型 Object.prototype。因为Object.prototype.__proto__ == null,所以也可以说原型链的尽头是 null 。
四、继承
说了原型链,那么自然要说原型链的核心作用——继承。
我们通过将 A 的原型指定为 B,那么 A 就可以通过原型链来继承 B 的属性与方法。
不过这种继承存在一些问题:
- 继承到的属性是全部实例共享的
- 创造子类型实例时,不能向超类型的构造函数中传递参数(子类继承父类时,不能特例化父类)
1. 借用构造函数继承
let superType = function(a) {
this.a = a;
}
let subType = function(a, b) {
superType.call(this, a);
this.b = b;
}
let obj = new subType(1, 2);
简介:在子类型的构造函数内部用 call 去调用超类型的构造函数
优点:可以在子类型构造函数中向超类型函数传递参数
缺点:无法做到函数复用
2. 组合继承
let superType = function(a) {
this.a = a;
}
superType.prototype.fun1 = function(){};
let subType = function(a, b) {
// 继承属性
superType.call(this, a);
this.b = b;
}
// 继承方法
subType.prototype = new superType();
subType.prototype.constructor = subType;
subType.prototype.fun2 = function(){};
let obj = new subType(1, 2);
简介:原型链继承 实现方法继承,借用构造函数继承 实现属性继承。
优点:完美结合了 原型链继承 和 构造函数继承 的优点
缺点:调用了两次超类型构造函数
3. 寄生组合式继承
let superType = function(a) {
this.a = a;
}
superType.prototype.fun1 = function(){};
let subType = function(a, b) {
// 继承属性
superType.call(this, a);
this.b = b;
}
// 继承方法
subType.prototype = Object.create(superType.prototype)
subType.prototype.constructor = subType;
subType.prototype.fun2 = function(){};
简介:组合式继承的改进。原型链继承方法时,只对原型进行操作继承。
优点:开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
4. ES6 class 继承
最后的呈现效果和 寄生组合式继承 比较相似:
- 子类实例继承的父类实例的属性
- 子类实例的原型指向了一个用父类实例原型创建的空对象,实现方法继承
4.1 class 继承和 寄生组合式继承 的不同
- 实例属性继承机制的不同:
- 寄生组合式继承:先创建子类实例 this,再执行父类构造函数来修改 this
- class 继承:先创建父类实例 this,再执行子类构造函数来修改 this
- 静态属性、方法的继承:
- class 通过将
子类.__proto__ = 父类实现父类静态属性、方法的继承
- class 通过将
- 原生构造函数的继承的不同:
- ES5 无法继承原生构造函数,比如 Array、Object
- class 可以继承原生构造函数来定义子类
4.2 class 的两条继承链
class 的继承有一点特殊,就是它有两条继承链:
子类.__proto__ = 父类(子类继承父类静态属性
方法)
子类.prototype.__proto__ = 父类.prototype(子类实例继承父类实例)
这两条继承链可以这样理解:
- 作为一个对象,子类的原型是父类
- 作为一个构造函数,子类的原型是父类的实例
4.3 super 关键字
这个关键字比较特殊,既可以当构造函数用,也可当对象使用。
- 构造函数:在子类的构造函数里,作为父类的构造函数被调用
- 对象:
2.1:在子类方法里用 super 调用父类方法时,super 指向父类原型
2.2:在子类静态方法里用 super 调用父类静态方法时,super 指向父类
2.3:在子类方法里通过 super 对某个属性赋值,super 指向子类实例本身(super 就是 this)

浙公网安备 33010602011771号