原型
- JavaScript 的所有对象中都包含了一个
__proto__
内部属性,这个属性所对应的就是该对象的原型 - JavaScript 的函数对象,除了原型
__proto__
之外,还预置了 prototype 属性 - 当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型
__proto__
。
原型链
任何一个实例对象通过原型链可以找到它对应的原型对象,原型对象上面的实例和方法都是实例所共享的。
一个对象在查找以一个方法或属性时,他会先在自己的对象上去找,找不到时,他会沿着原型链依次向上查找。
注意: 函数才有 prototype,实例对象只有有proto, 而函数有的proto是因为函数是 Function 的实例对象
instanceof 原理
判断实例对象的proto属性与构造函数的 prototype 是不是用一个引用。如果不是,他会沿着对象的proto向上查找的,直到顶端 Object。
判断对象是哪个类的直接实例
使用对象.construcor
直接可判断
类
类的声明
// 普通写法
function Animal() {
this.name = 'name'
}
// ES6
class Animal2 {
constructor() {
this.name = 'name'
}
}
继承
1、原型链继承 - Child.prototype = new Parent();
原理:把子类的 prototype(原型对象)直接设置为父类的实例
缺点:因为子类只进行一次原型更改,所以子类的所有实例保存的是同一个父类的值。 当子类对象上进行值修改时,如果是修改的原始类型的值,那么会在实例上新建这样一个值; 但如果是引用类型的话,他就会去修改子类上唯一一个父类实例里面的这个引用类型,这会影响所有子类实例
function Parent() {
this.name = "parent";
this.arr = [1, 2, 3];
}
function Child() {
this.type = "child";
}
Child.prototype = new Parent();
var c1 = new Child();
var c2 = new Child();
c1.__proto__ === c2.__proto__;
2、构造函数继承 - Parent.call(this)
在构造函数中 使用Parent.call(this)
的方法继承父类属性。
原理: 将子类的 this 使用父类的构造函数跑一遍
缺点: Parent 原型链上的属性和方法并不会被子类继承
function Parent(name, id){
this.id = id;
this.name = name;
this.printName = function(){
console.log(this.name);
}
}
Parent.prototype.sayName = function(){
console.log(this.name);
};
function Child(name, id){
Parent.call(this, name, id);
// Parent.apply(this, arguments);
}
var child = new Child("jin", "1");
child.printName(); // jin
child.sayName() // Error
3、组合继承
组合构造函数中使用 call 继承和原型链继承。
原理:子类构造函数中使用Parent.call(this);
的方式可以继承写在父类构造函数中 this 上绑定的各属性和方法;
使用Child.prototype = new Parent()
的方式可以继承挂在在父类原型上的各属性和方法
缺点: 父类构造函数在子类构造函数中执行了一次,在子类绑定原型时又执行了一次
function Parent(name, id){
this.id = id;
this.name = name;
this.list = ['a'];
this.printName = function(){
console.log(this.name);
}
}
Parent.prototype.sayName = function(){
console.log(this.name);
};
function Child(name, id){
Parent.call(this, name, id);
// Parent.apply(this, arguments);
}
Child.prototype = new Parent();
var child = new Child("jin", "1");
child.printName(); // jin
child.sayName() // jin
var a = new Child();
var b = new Child();
a.list.push('b');
console.log(b.list); // ['a']
4、原型式继承 - 添加中间对象
原理:类似 Object.create,用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象,结果是将子对象的proto指向父对象
缺点:共享引用类型
var parent = {
names: ['a']
}
function copy(object) {
function F() {}
F.prototype = object;
return new F();
}
var child = copy(parent);
5、寄生式继承
原理:二次封装原型式继承,并拓展 优点:可添加新的属性和方法
function createObject(obj) {
var o = copy(obj);
o.getNames = function() {
console.log(this.names);
return this.names;
}
return o;
}
6、寄生组合继承
原理:改进组合继承,利用寄生式继承的思想继承原型
function inheritPrototype(subClass, superClass) {
// 复制一份父类的原型
var p = copy(superClass.prototype);
// 修正构造函数
p.constructor = subClass;
// 设置子类原型
subClass.prototype = p;
}
function Parent(name, id){
this.id = id;
this.name = name;
this.list = ['a'];
this.printName = function(){
console.log(this.name);
}
}
Parent.prototype.sayName = function(){
console.log(this.name);
};
function Child(name, id){
Parent.call(this, name, id);
// Parent.apply(this, arguments);
}
inheritPrototype(Child, Parent);
封装一个原生的继承方法
/**
* 继承
* @param Parent
* @param Child
*/
function extendsClass(Parent, Child) {
function F() {}
F.prototype = Parent.prototype
Child.prototype = new F()
Child.prototype.constrctor = Child
return Child
}
ES5/ES6 的继承除了写法以外还有什么区别?
- class 声明会提升,但不会初始化赋值。Foo 进入暂时性死区,类似于 let、const 声明变量。
- class 声明内部会启用严格模式。
- class 的所有方法(包括静态方法和实例方法)都是不可枚举的。
- class 的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所以也没有[[construct]],不能使用 new 来调用。
- 必须使用 new 调用 class。
- class 内部无法重写类名。