一、原型
在js中,每当定义一个函数数据类型(Object、Function、Arrry、Date等)的时候都会自带一个 prototype 对象,这个对象就是原型。
每个对象都有一个 __proto__ 属性,指向创建该对象的函数的 prototype。
function Person(){
this.showNam e= function () {
console.log("zhangsan");
};
}
let person1 = new Person();
let person2 = new Person();
console.log(person1.showName === person2.showName); // false
很容易就知道输出的是 false,因为两个实例对象指向的不是同一个地方。
那再看下面这个例子:
function Person(name){
this.name = name;
}
Person.prototype.showName = function () {
console.log(this.name);
};
let person1 = new Person("zhangsan");
let person2 = new Person("lisi");
console.log(person1.showName === person2.showName) // true
这样输出的结果就是 true 了,因为两个实例对象共享同一个方法。所以就能知道原型的作用就是实现数据的共享。
二、原型链
访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着 __proto__这条链向上找,这就是原型链。
简单来说就是原型与原型之间层层链式连接的过程称为原型链,原型链是通过 __proto__ 链接起来的,所以为什么实例对象可以使用构造函数的 prototype 原型上的属性和方法,就是因为实例对象有 __proto__ 的存在,例子:
function Person() {}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
最终输出的就是 true。
三、基于原型链的查找方式
function Person(){
this.name = "zhangsan";
}
Person.prototype.name = "lisi";
Object.prototype.name = "wangwu";
let person1=new Person();
console.log(person1.name); // "zhangsan"
(1) 首先 person1 对象会在本身查找,看自己有没有 name 属性,如果有则查找结束;
(2) 如果本身没有就会沿着原型链去原型上找,找到了就结束;
(3) 如果原型链上还是没有,就会去 Object 原型对象上去查找;
(4) 如果还没有则会报错。
四、继承
由于所有对象的原型链都会找到 Object.prototype,因此所有对象都会有 Object.prototype 的方法。这就是继承。
function People(name) {
//属性
this.name = name || 'akl';
//实例方法
this.sleep = function() {
console.log(this.name + '正在睡觉');
};
}
//原型方法
People.prototype.eat = function (food) {
console.log(this.name + '正在吃:' + food);
};
1. 原型链继承
父类的实例作为子类的原型:
function Woman(){
}
Woman.prototype = new People();
Woman.prototype.name = 'akl';
let womanObj = new Woman();
优点:简单易于实现;父类新增的实例与属性子类都能访问。
缺点:可以在子类中增加实例属性,如果要新增加原型属性和方法需要在 new 父类构造函数的后面;无法实现多继承;创建子类实例时,不能向父类构造函数中传参数。
2. 借用构造函数继承(伪造对象,经典继承)
复制父类的实例属性给子类:
function Woman(name) {
// 继承了People
People.call(this); // People.call(this,'akl');
this.name = name || 'renbo';
}
let womanObj = new Woman();
优点:解决了子类构造函数向父类构造函数中传递参数;可以实现多继承(call 或者 apply 多个父类)。
缺点:方法都在构造函数中定义,无法复用;不能继承原型属性/方法,只能继承父类的实例属性和方法
3. 实例继承(原型式继承)
function Woman(name) {
let instance = new People();
instance.name = name || 'akl';
return instance;
}
let womanObj = new Woman();
优点:不限制调用方式;简单,易实现;
缺点:不能多继承。
4. 组合式继承
调用父类构造函数,继承父类的属性,通过将父类实例作为子类原型,实现函数复用。
function People(name, age) {
this.name = name || 'akl';
this.age = age || 27;
}
People.prototype.eat = function(){
return this.name + this.age + 'eat sleep';
};
function Woman(name, age){
People.call(this, name, age);
}
Woman.prototype = new People();
Woman.prototype.constructor = Woman;
let wonmanObj = new Woman("ren", 27);
console.log(wonmanObj.eat()); // ren27eat sleep
优点:函数可以复用;不存在引用属性问题;可以继承属性和方法,并且可以继承原型的属性和方法;
缺点:由于调用了两次父类,所以产生了两份实例。