JS原型和继承的简单理解

168 阅读3分钟

JS对象和构造函数

  • 对象分为普通对象和函数对象,函数对象都是通过new Function创造出来的
  • 函数对象分为普通函数和构造函数,区别如下:
    • 作用: 构造函数目的新建实例对象
    • 首字母大小: 普通函数用小写,构造函数习惯用大写
    • 调用方式:普通函数直接调用,构造函数需要使用new关键字来调用
    • 函数中this: 普通函数中的this,在严格模式下指向undefined,非严格模式下指向window对象。构造函数的this则是指向它创建的对象实例。

原型对象

js每定义一个对象后,都会有几个预定义属性,其中函数对象的一个属性就是原型对象prototype, 普通对象没有prototype;

  • 原型对象本身是一个普通对象
  • 原型对象主要用作继承
  • 原型对象拥有公有的属性和方法

原型链

在js 中每个对象(null 除外)都有一个属性__proto__, 该属性指向原型对象,换句话说则是:该对象是从那个原型对象继承属性和方法,我们可以通过此属性来找到,原型链则是基于__proto__来实现的。 当我们访问一个对象的属性或者方法的时候,会先去对象自身找这个属性或者方法,如果没有找到,则会去它的原型对象身上找,如果原型对象身上也没有找到,则会到原型对象的原型对象上去找,以此类推,找到则返回对应的值,如果直到原型对象为null任然没有找到则返回undefined。

function Fn(name) {
  this.name = name
}
Fn.__proto__ === Fn.prototype // true
Fn.__proto__.__proto__ ===  Fn.prototype.__proto__=== Object.prototype // rue
Objcet.prototype.__proto__ === null // true

原型链继承

function Parent() {
 this.property = 888;
}
Parent.prototype.getMaxProperty = function() {
 return this.property = this.property * 10;
};
function Child() {
 this.property = 1;
}
// 继承 Parent
Child.prototype = new Parent(); 
let child1 = new Child();
child1.getMaxProperty()  //10 已继承父级方法
  • 问题一 以对象字面量方式修改、创建原型方法会破坏之前的原型链
Child.prototype = new Parent(); 
//通过对象字面量添加新方法,这会导致上一行无效
Child.prototype = {
	doSomething() {
     return '工作'
    },
    com: 'human'
}
let child2 = new Child();
child2.getMaxProperty();  //报错,父级原型链已丢失
child2.doSomething(); // '工作'  新的原型链
child2.com // 'human' 新的原型链

解决办法: 原型对象prototype中constructor属性指向是构造函数本身,同时需改更改属性是否可以枚举。

Child.prototype = {
	doSomething() {
     return '工作'
    },
    com: 'human'
}
Object.defineProperty(Child.prototype, 'constructor', {
    enumerable: false,
    value: Parent
})

  • 问题二 原型中包含的引用值会在所有实例间共享
function SuperType() {
 this.colors = ["red", "blue", "green"];
}
function SubType() {}
// 继承 SuperType
SubType.prototype = new SuperType();
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
let instance2 = new SubType();
console.log(instance2.colors); // "red,blue,green,black" 
  • 问题三 子类型在实例化时不能给父类型的构造函数传参

构造函数继承(call&apply)

在子类 构造函数中调用父类构造函数。因为毕竟函数就是在特定上下文中执行代码的简单对象,所以可以使用 apply()和 call()方法以新创建的对象为上下文执行构造函数。

function SuperType() {
 this.colors = ["red", "blue", "green"];
}
function SubType() {
 // 继承 SuperType
 SuperType.call(this);
}
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
let instance2 = new SubType();
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.sayAge = function() {
 console.log(this.age);
};
let instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
instance1.sayName(); // "Nicholas";
instance1.sayAge(); // 29
let instance2 = new SubType("Greg", 27);
console.log(instance2.colors); // "red,blue,green"
instance2.sayName(); // "Greg";
instance2.sayAge(); // 27 
  • 缺点: 父类的构造器被调用了两次

寄生组合继承

function inheritPrototype(subType, superType) {
 let prototype = object(superType.prototype); // 创建对象
 prototype.constructor = subType; // 增强对象
 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);
}; 

new创建一个对象,执行构造函数。 Object.create相当于创建一个对象,但是不执行构造函数。