一、 原型 / 构造函数 / 实例 关系
-
prototype 原型
原型指的就是一个对象,实例通过继承获取原型上的数据
-
instance 实例
有个构造函数,我们就可以在原型上创建可以“继承”的属性,并通过 new 操作符创建实例
-
proto 隐式原型
实例通过
__proto__
访问到原型,所以如果是实例,那么就可以通过这个属性直接访问到原型 -
constructor 构造函数
既然构造函数通过 prototype 来访问到原型,那么原型也应该能够通过某种途径访问到构造函数,这就是 constructor
-
实例 构造函数 原型之间的关系
// 构造函数 function Person(){}; // Person原型上添加属性 Person.prototype.type = 'object named Person'; // 实例 person const person = new Person(); // 他们之间的关系 Person.prototype.constuctor === Person; // true person.__proto__ === Person.prototype; // true Person.prototype.__proto__ === Object.prototype; // true
-
原型链
原型同样也可以通过
__proto__
访问到原型的原型,比方说这里有个构造函数 Person 然后“继承”前者的有一个构造函数 People,然后 new People 得到实例 p当访问 p 中的一个非自有属性的时候,就会通过
__proto__
作为桥梁连接起来的一系列原型、原型的原型、原型的原型的原型直到 Object 构造函数为止。这个搜索的过程形成的链状关系就是原型链
function F(){}
var f = new F();
// 构造器
F.prototype.constructor === F; // true
F.__proto__ === Function.prototype; // true
Function.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
// 实例
f.__proto__ === F.prototype; // true
F.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
二、继承
-
原型链继承
父类的实例当做子类的原型。如此子类的原型包含父类定义的实例属性,享有父类原型定义的的属性。
// 父类 function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; }; // 子类 SubType function SubType() {} SubType.prototype = new SuperType(); // 实例 const instance = new SubType(); console.log(instance); console.log(instance.getSuperValue()); // true console.log(instance instanceof SubType); // true console.log(instance instanceof SuperType); // true console.log(instance instanceof Object); // true console.log(SubType.prototype.isPrototypeOf(instance)); // true console.log(SuperType.prototype.isPrototypeOf(instance)); // true console.log(Object.prototype.isPrototypeOf(instance)); // true
缺点:
- 来自原型对象的引用属性是所有实例共享的
- 创建子类实例时,无法向父类构造函数传参
-
借用构造函数
子类直接使用父类的构造函数。如此子类的实例直接包含父类定义的实例属性。
// 父类 SuperType function SuperType (name) { this.name = name; this.colors = ['red', 'blue', 'green']; this.getName = function () { return this.name; } } // 子类 function SubType (name) { // 继承了SuperType,同时还传递了参数 SuperType.call(this, name); // 实例属性 this.age = 20; } // 实例 const instance1 = new SubType('Tom'); instance1.colors.push('black'); console.log(instance1.name); // "Tom" console.log(instance1.getName()); // "Tom" console.log(instance1.age); // 20 console.log(instance1.colors); // ['red', 'blue', 'green', 'black'] const instance2 = new SubType('Peter'); console.log(instance2.name); // "Peter" console.log(instance2.getName()); // "Peter" console.log(instance2.age); // 20 console.log(instance2.colors); // ['red', 'blue', 'green']
缺点:
- 子类不能继承父类原型上的属性
- 如果方法都在构造函数内,不在原型上,因此方法不能复用,每个实例中都有一份方法,因此造成了内存的浪费
-
组合继承
将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
// 父类 function SuperType(name) { this.name = name; this.colors = ['red', 'blue', 'green']; } SuperType.prototype.sayName = function () { console.log(this.name); } // 子类 function SubType(name, age) { // 继承父类实例属性 SuperType.call(this, name); // 第一次调用构造函数 // 子类实例属性 this.age = age; } SubType.prototype = new SuperType(); // 第二次调用构造函数 SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () { console.log(this.age); }; // 实例 const instance1 = new SubType('Tom', 20); instance1.colors.push('black'); console.log(instance1.colors); // ['red', 'blue', 'green', 'black'] instance1.sayName(); // "Tom" instance1.sayAge(); // 20 const instance2 = new SubType('Peter', 30); console.log(instance2.colors); // ['red', 'blue', 'green'] instance2.sayName(); // "Peter" instance2.sayAge(); // 30
缺点:
- 调用了两次父类构造函数,一次通过SuperType.call(this)调用,一次通过new SuperType()调用。
-
原型式继承
不使用严格意义上的构造函数,借助原型可以基于已有的对象创建新的对象,同时还不必因此创建自定义类型
// 在object函数内部,先创建了一个临时的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。 // 从本质上讲,object()对传入其中的对象执行了一次浅复制。 function object (o) { function F() {} F.prototype = o; return new F(); } var person = { name: 'Tom', friends: ['Shelby', 'Court', 'Van'] }; var anotherPerson = object(person); anotherPerson.name = 'Greg'; anotherPerson.friends.push('Rob'); var yetAnotherPerson = object(person); yetAnotherPerson.name = 'Linda'; yetAnotherPerson.friends.push('Barbie'); console.log(anotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie'] console.log(yetAnotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie'] console.log(person.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie']
缺点:
- 和原型链继承一样,所有子类实例共享父类的引用类型
-
寄生式继承
寄生式继承是与原型式继承紧密相关的一种思路,创建一个仅用于封装继承过程的函数,该函数内部以某种形式来做增强对象,最后返回对象
function object (o) { function F() {} F.prototype = o; return new F(); } function createAnother (o) { var clone = object(o); clone.sayHi = function () { console.log('Hi'); } return clone; } var person = { name: 'Tom', friends: ['Shelby', 'Court', 'Van'] }; var anotherPerson = createAnother(person); anotherPerson.sayHi(); // "Hi" anotherPerson.friends.push('Rob'); console.log(anotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob'] var yerAnotherPerson = createAnother(person); console.log(yerAnotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob']
缺点:
和原型链式继承一样,所有子类实例共享父类引用类型。
和借用构造函数继承一样,每次创建对象都会创建一次方法。
-
寄生组合式继承
// 寄生继承 function object(o) { function F() {} F.prototype = o; return new F(); } // 将父类的prototype继承给子类 function inheritPrototype(SubType, SuperType) { // 将父类的prototype 赋值给 第三类 F 的 prototype上,并将此类的实例缓存到 prototype 变量上 const prototype = object(SuperType.prototype); // 将第三类 F 的实例的构造函数指向 SubType prototype.constructor = SubType; // 将 SubType 原型指向 第三类 F 的实例 SubType.prototype = prototype; } // 父类 function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function () { console.log(this.name); }; // 子类 function SubType(name, age) { // 继承父类实例属性 SuperType.call(this, name); // 子类实例属性 this.age = age; } // 继承父类方法 inheritPrototype(SubType, SuperType); // 子类方法 SubType.prototype.sayAge = function () { console.log(this.age); }; // 实例 const instance1 = new SubType('Tom', 20); instance1.colors.push('black'); instance1.sayAge(); // 20 instance1.sayName(); // "Tom" console.log(instance1.colors); // ["red", "blue", "green", "black"] const instance2 = new SubType('Peter', 30); instance2.sayAge(); // 30 instance2.sayName(); // "Peter" console.log(instance2.colors); // ["red", "blue", "green"]
-
对象冒充
function Person(name,age){ this.name = name; this.age = age; this.show = function(){ console.log(this.name+", "+this.age); } } function Student(name,age){ this.student = Person; //将Person类的构造函数赋值给this.student this.student(name,age); //js中实际上是通过对象冒充来实现继承的 delete this.student; //移除对Person的引用 } var s = new Student("小明",17); s.show(); var p = new Person("小花",18); p.show(); // 小明, 17 // 小花, 18
-
ES6实现继承
// 父类 class SuperType { constructor(name) { this.name = name; this.colors = ["red", "blue", "green"]; } sayName() { console.log(this.name); }; } // 子类 class SubType extends SuperType { constructor(name, age) { // 继承父类实例属性和prototype上的方法 super(name); // 子类实例属性 this.age = age; } // 子类方法 sayAge() { console.log(this.age); } } // 实例 var instance1 = new SubType('Tom', 20); instance1.colors.push('black'); instance1.sayAge(); // 20 instance1.sayName(); // "Tom" console.log(instance1.colors); // ["red", "blue", "green", "black"] var instance2 = new SubType('Peter', 30); instance2.sayAge(); // 30 instance2.sayName(); // "Peter" console.log(instance2.colors); // ["red", "blue", "green"]
可以看到,底层其实也是用寄生组合式继承来实现的。
四、new 运算符到底做了哪些
function A(){};
const a = new A();
当代码运行时候,内部实际上执行了这几步
- 创建一个空的简单JavaScript对象(即
{}
);- 链接该对象(即设置该对象的构造函数)到另一个对象 ;
- 将步骤1新创建的对象作为
this
的上下文 ;- 如果该函数没有返回对象,则返回
this
。
// 1. 创建一个空的简单JavaScript对象(即`{}`);
const o = new Object();
// 2. 链接该对象(即设置该对象的构造函数)到另一个对象
o.__proto__ = A.prototype;
// 3. 将步骤1新创建的对象作为 this 的上下文
A.call(o);
- 如果该函数没有返回对象,则返回 this
function A(name){
this.name = name;
return {
age: 12
}
}
var a = new A('tom');
console.dir(a);
/*
Object
age:12
__proto__:Object
*/
参考:
JS原型、原型链、构造函数、实例与继承
new 运算符