写在开头
在刷掘金的时候,看到大佬在一篇文章下的评论,好精辟
- 所有的构造函数都是 Function 的实例
- 所有原型对象都是 Object 的实例,除了
Object.prototype
概念
-
构造函数、原型和实例的关系
每个构造函数(constructor)都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针,而实例(instance)都包含一个指向原型对象的内部指针。
-
原型链
在对象向上查找过程中,prototype 在这个规则中充当链接的作用,于是我们把这种实例与原型的链条称为原型链。
四个规则
引用类型的四个规则
-
引用类型,都具有对象特性,即可自由扩展属性。
jsconst obj = {} const arr = [] const fn = function () {} obj.a = 1 arr.a = 1 fn.a = 1 console.log(obj.a) // 1 console.log(arr.a) // 1 console.log(fn.a) // 1
-
引用类型,都有一个隐式原型
__proto__
属性,属性值是一个普通的对象jsconst obj = {} const arr = [] const fn = function () {} console.log('obj.__proto__', obj.__proto__) console.log('arr.__proto__', arr.__proto__) console.log('fn.__proto__', fn.__proto__)

-
引用类型,隐式原型
__proto__
的属性值指向他的构造函数的显式原型prototype
属性值jsconst obj = {} const arr = [] const fn = function () {} console.log(obj.__proto__ === Object.prototype) // true console.log(arr.__proto__ === Array.prototype) // true console.log(fn.__proto__ === Function.prototype) // true
-
当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么他会去查询它的隐式原型
__proto__
中寻找jsconst obj = { a: 1 } obj.toString // ƒ toString() { [native code] }
obj
本身没有toString
属性,所以会去查询它的隐式原型__proto__
中寻找,在Object
的prototype
找到了,所以obj.toString
的值就是toString
属性的值。
引用类型:Object、Array、Function、Date、RegExp。
一个特例
js
function Person(name) {
this.name = name
}
let nick = new Person('nick')
console.log(nick.toString) // ƒ toString() { [native code] }
正常来说,nick
是 Person
构造函数生成的实例,而 person
的 prototype
并没有 toString
方法,但是为什么 nick
能获取到 toString
方法呢?
这里就引出了 原型链
的概念了,nick
的实例先从自身出发检讨自己,发现并没有 toString
方法。找不到,就往上找,找 Person
构造函数的 prototype
属性值,还是没有找到。构造函数的 prototype
也是一个对象,对象的构造函数是 Object
, 所以就找到了 Object.prototype
下的 toString
方法。

一张图片

原型上的方法
instanceof
运算符用于测试构造函数的 prototype
属性是否出现在对象原型链中的任何位置。
手写 instanceof
js
// 变量 right 的原型是否存在于 left 的原型链上
function instance_of(left, right) {
// 验证如果为基本数据类型,则直接返回 false
const baseType = ['string', 'number', 'boolean', 'undefined', 'symbol']
if (baseType.includes(typeof left)) return false
let RightP = right.prototype;
let LeftP = left.__proto__;
while (true) {
if (LeftP === null) {
return false // 代表找到了最顶层
}
if (LeftP === RightP) { // 严格相等
return true
}
LeftP = LeftP.__proto__ // 继续向上查找
}
}
再来看一段代码
js
function Foo(name) {
this.name = name
}
let f = new Foo('nick')
f instanceof Foo // true
f instanceof Object // true
尝试理解一下判断流程
f instanceof Foo
:f
的隐式原型__proto__
和Foo.prototype
是同一个对象,所以返回true
f instanceof Object
:f
的隐式原型__proto__
和Object.prototype
不等,所以继续向上查找,f
的隐式原型__proto__
指向Foo.prototype
,所以继续用Foo.prototype
的隐式原型__proto__
去对比Object.prototype
,所以返回true
hasOwnProperty
hasOwnProperty 方法用于检测一个对象是否含有特定的自身属性(即,不能从原型上继承的属性)。
js
console.log(instance1.hasOwnProperty('name')) // true
isPrototypeOf
isPrototypeOf 方法用于测试该对象是否为指定对象的原型。
js
console.log(Father.prototype.isPrototypeOf(instance1)) // true
原型链的问题
- 当原型链中包含引用类型值的原型时,该引用类型值会被所有实例所共享。
- 在创建子类型时,不能向超类型的构造函数中传递参数。
可以尝试弥补原型链的不足
借用构造函数(经典继承)
在子类型构造函数的内部调用超类型构造函数。
js
function Father() {
this.colors = ['red', 'blue', 'green'];
}
function Son() {
Father.call(this); // 继承了 Father 的属性和方法, 且可以向父类型传递参数
}
let instance1 = new Son();
instance1.colors.push('black');
console.log(instance1.colors); // ["red", "blue", "green", "black"]
let instance2 = new Son();
console.log(instance2.colors); // ["red", "blue", "green"]
借用构造函数解决了原型链的两大问题
- 保证了原型链中引用类型的值的独立,不再被所有实例所共享。
- 可以向超类型构造函数中传递参数。
组合继承
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。 既通过在原型上定义方法实现了函数复用,又能保证每个实例都有她自己的属性。
js
function Father(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Father.prototype.sayName = function () {
console.log(this.name);
}
function Son(name, age) {
Father.call(this, name); // 继承示例属性,第一次调用 Father()
this.age = age;
}
Son.prototype = new Father(); // 继承父类方法,第二次调用 Father()
Son.prototype.sayAge = function () {
console.log(this.age);
};
let instance1 = new Son('nick', 18);
instance1.colors.push('black');
console.log(instance1.colors); // ["red", "blue", "green", "black"]
instance1.sayName(); // nick
instance1.sayAge(); // 18
let instance2 = new Son('jack', 20);
console.log(instance2.colors); // ["red", "blue", "green"]
instance2.sayName(); // jack
instance2.sayAge(); // 20
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,是 JavaScript 中最常用的继承模式。 而且 instanceOf 和 isPrototypeOf 也能用于识别基于组合继承创建的对象。
原型继承
在 object() 函数内部,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。
js
function createObject(o) {
function F() {
}
F.prototype = o;
return new F();
}
本质上讲,createObject()
函数就是返回了一个引用传入对象的新对象。这样可能会有共享数据的问题。
在 ECMAScript5 中,通过新增 Object.create()
方法,规范了上面的原型继承。
Object.create() 方法接受两个参数:
- 一个用作新对象原型(prototype)的对象
- (可选) 一个为新对象定义额外属性的对象,这个对象与 Object.defineProperties() 方法的参数对象格式相同。以这种方式指定的任何属性都会覆盖新对象原型对象上的同名属性。
原型继承中,包含引用类型值的属性始终都会共享相应的值
寄生式继承
寄生式继承就是创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回对象。
寄生组合式继承
寄生组合式继承就是为了降低调用父类构造函数的开销而出现的
基本思想:不必为了指定子类型的原型而调用超类型的构造函数
js
function extend(subClass, superClass) {
// 创建了一个新对象,这个对象的原型是父类的 superClass.prototype, 也就是说 `prototype` 现在是一个继承了 superClass.prototype 的新对象。
let prototype = Object.create(superClass.prototype);
// 默认情况下,使用 Object.create() 创建的对象的 constructor 会指向 superClass,因此我们需要将它重新设置回 subClass,保持构造函数引用的正确性。
prototype.constructor = subClass;
// 把 subClass.prototype 设置为构建好的 prototype,这样就完成了继承关系的建立。
subClass.prototype = prototype;
}
new 运算符
js
let obj = {};
obj.__proto__ = F.prototype;
F.call(obj);
- 第一行,创建了一个空对象obj。
- 第二行,我们将这个空对象的__proto__设置为了F.prototype。
- 第三行,我们将F函数对象的this指向了obj,然后再调用F函数。
在 new 操作符的作用下,实际上发生了以下事情:
- 创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
- 属性和方法被添加到 this 引用的对象上。
- 新创建的对象由 this 所引用,并且最后隐式的返回 this。