一、构造函数
- 原型对象
funciton A(name) {
this.name = name;
this.say = function() {
console.log(this.name)
}
}
所有的实例是共享同一个原型对象,因此有别于实例方法或属性,原型对象仅有一份。而实例有很多份,且实例属性和方法是独立的。
在构造函数中,应当保持属性的私有性、以及方法的复用、共享。
所以:
- 将属性封装在构造函数中
- 将方法定义在原型对象上
let test1 = new A('test1');
let test2 = new A('test'2);
test1.say () // test1
test1.say () // test2
test1.say === test.say //false
如果将函数放在构造函数中,虽然可以在每个实例对象中实现,但是每个实例都用的是同一套函数逻辑,这就造成了性能的浪费
推荐的写法
funcion A(name) {
this.name = name;
}
A.prototype.say = function() {
console.log(this.name)
}
二、各种继承方式
1. 原型链继承
function Parent(name) {
this.name = name;
this.colors = ['red', 'yellow', 'green'];
}
Parent.prototype.say = function() {
console.log(this.name)
}
function Child() {}
Child.prototype = new Praent();
// 所有涉及到原型链继承的继承方式都要修改子类构造函数的指向,否则子类实例的构造函数会指向SuperType。
Child.prototype.constructor = Child;
let child1 = new Child('child1');
let child2 = new Child('child2');
child1.say() // undefined
child2.say() // undefined
child1.say === child2.say // true
child1.colors.push('blue');
child1.colors; // ['red', 'yellow', 'green', 'blue'];
child2.colors; // ['red', 'yellow', 'green', 'blue'];
优点:
- 父类方法可以复用
缺点:
- 父类的引用属性会被所有子类实例共享 2. 子类构建实例时不能向父类传递参数
2. 构造函数继承
function Parent(name) {
this.name = name;
this.colors = ['red', 'yellow', 'green'];
}
Parent.prototype.say = function() {
console.log(this.name)
}
function Child(name) {
Parent.call(this, name)
}
Child.prototype.constructor = Child;
let child1 = new Child('child1');
let child2 = new Child('child2');
child1.say() // // child1.say is not a function
child1.colors.push('blue');
child1.colors; // ['red', 'yellow', 'green', 'blue'];
child2.colors; // ['red', 'yellow', 'green'];
优点:
- 父类的引用属性不会被共享 2. 子类构建实例时可以向父类传递参数
缺点:
- 不能继承父类原型上的方法
3. 组合继承
function Parent(name) {
this.name = name;
this.colors = ['red', 'yellow', 'green'];
}
Parent.prototype.say = function() {
console.log(this.name)
}
function Child(name) {
Parent.call(this, name); // 第二次调用
this.age = 18;
}
Child.prototype = new Parent(); // 第一次调用
Child.prototype.constructor = Child; // 注意修复构造函数指向
let child1 = new Child('child1');
let child2 = new Child('child2');
child1.say() // 'child1'
child2.say() // 'child2'
child1.say === child2.say // true
child1.colors.push('blue');
child1.colors; // ['red', 'yellow', 'green', 'blue'];
child2.colors; // ['red', 'yellow', 'green'];
优点:
- 父类的方法可以被复用
- 父类的引用属性不会被共享
- 子类构建实例时可以向父类传递参数
缺点:
- 调用了两次父类的构造函数
4. 原型式继承
核心:
- 原型式继承的object方法本质上是对参数对象的一个浅复制。
优点:
- 父类方法可以复用
缺点:
- 父类的引用属性会被所有子类实例共享
- 子类构建实例时不能向父类传递参数
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var parent = {
name: "parent",
colors:['red', 'yellow', 'green']
};
let child1 = object(parent);
let child2 = object(parent);
child1.name = 'child1'; // 'child1'
child1.colors.push('blue')
child1.colors; // ['red', 'yellow', 'green', 'blue'];
5. 寄生继承
使用原型式继承获得一个目标对象的浅复制,然后增强这个浅复制的能力。
function createAnother(original){
var clone=object(original); //通过调用函数创建一个新对象
clone.say= function(){ //以某种方式来增强这个对象
console.log(this.name);
};
return clone; //返回这个对象
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
6. 寄生组合继承
这是一种完美的继承方式。
function Parent(name) {
this.name = name;
this.colors = ['red', 'yellow', 'green'];
}
Parent.prototype.say = function() {
console.log(this.name)
}
function Child(name,age) {
Parent.call(this,name,age)
this.age = age;
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
let child1 = new Child('child1');
let child2 = new Child('child2');
child1.say() // 'child1'
child2.say() // 'child2'
child1.say === child2.say // true
child1.colors.push('blue');
child1.colors; // ['red', 'yellow', 'green', 'blue'];
child2.colors; // ['red', 'yellow', 'green'];
7. ES6 继承
ES6继承的结果和寄生组合继承相似,本质上,ES6继承是一种语法糖。但是,寄生组合继承是先创建子类实例this对象,然后再对其增强;而ES6先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
ES6实现继承的具体原理
class A {
}
class B {
}
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype);
// B 继承 A 的静态属性
Object.setPrototypeOf(B, A);
使用
class A {}
class B extends A {
constructor() {
super();
}
}
ES6继承与ES5继承的异同:
相同点:
- 本质上ES6继承是ES5继承的语法糖
不同点:
- ES6继承中子类的构造函数的原型链指向父类的构造函数,ES5中使用的是构造函数复制,没有原型链指向。
- ES6子类实例的构建,基于父类实例,ES5中不是。
三、其他
1.Object.create()
Object.create() 的内部原理:
// 其中,o 是新创建对象的原型(对象)
function object(o) {
function F() {}
F.prototype = o
return new F()
}
2. new 的过程
-
创建一个空对象obj
let obj = new Object()
-
设置原型链
obj.__proto__ = Func.prototype 就是:将新对象的__proto__ 指向构造函数的prototype
-
将构造函数Func的this指向obj,并执行构造函数Func
let result = Func.call(obj) 就是:使用call或apply,将构造函数的this绑定到新对象,并执行构造函数
-
判断构造函数Func的返回值类型
如果是引用类型,就返回这个引用类型的对象。如果是值类型或没有return,则返回空对象obj。 if (typeof(result) === "object"){ func=result; } else{ func=obj; // 默认返回 } 注意:js中的构造函数,是不需要有返回值的,所以默认返回的是新创建的空对象obj
链接
js继承、构造函数继承、原型链继承、组合继承、组合继承优化、寄生组合继承
JS中的继承
一篇文章理解JS继承
六种Js中常见的继承方式(图解)