原型链
基本概念
- prototype: 显式原型
- __ proto__: 隐式原型
规则
- 实例拥有 new时默认是 this 构造函数的属性和构造函数原型上的方法
- 引用类型,都具有对象特性,即可自由扩展属性。
- 引用类型,都有一个隐式原型
__proto__属性,属性值是一个普通的对象 - 引用类型,隐式原型
__proto__的属性值指向它的构造函数的显式原型prototype属性值。 - 当你试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么它会去它的隐式原型
__proto__(也就是它的构造函数的显式原型prototype)中寻找。
原型 原型对象 实例 关系图(掘金上找的)
function People (name = 'People') {
this.name = name
}
People.prototype.say = function () {
console.log('我是一个', this.name)
}
let people = new People()
console.log(People.prototype.constructor === People) // true
console.log(People.prototype === people.__proto__) // true
原型链 关系图(掘金上找了几张好理解的)
instanceof操作符 、isPrototypeOf方法
instanceof 检测实例(instance)的原型链中 出现过的 构造函数的原型对象(prototype)
function Foo(name) {
this.name = name;
}
let f = new Foo('nick')
f instanceof Foo // true
f instanceof Object // true
isPrototypeOf() 原型链中出现过的原型
Foo.prototype.isPrototypeOf(f) // true
继承
基础代码 (后面代码在此基础上实现基础)
function People (name= 'People') {
this.name = name
}
People.prototype.say = function () {
console.log('我是一个', this.name)
}
function Women (age,name) {
this.age = age
}
1、原型链继承
let people = new People()
Women.prototype = people
let women = new Women(18,'women')
缺点
- 无法传值给父类
- 当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享
2、借用构造函数
function Women (age,name) {
this.age = age
Peple.call(this,name)
}
缺点
- 子级 只能 获取 父级 构造函数内定的属性和方法 ,获取不到父级原型对象的方法
- 原型链上关系不清晰 ,不可使用 instanceof 、isPrototypeOf方法检测
3、组合式继承
原型链继承 + 借用构造函数继承
缺点 两次调用 父级 构造函数 造成不必要的消耗
4、原型式继承
在object()函数内部, 先创建一个临时性的构造函数, 然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例
function object(obj){
function F(){}
F.prototype = obj;
return new F();
}
let women = object(new People('women'))
缺点 对父级仅仅是浅复制
在 ECMAScript5 中,通过新增 object.create() 方法规范化了上面的原型式继承.
object.create() 只有一个参数时功能与上述object方法相同, 它的第二个参数与Object.defineProperties()方法的第二个参数格式相同
var person = { name : "Ammy" };
var anotherPerson = Object.create(person, {
name : { value : "Luci" }
});
alert(anotherPerson.name);//"Luci"
5、寄生式继承
function createAnother(original){
var clone = object(original); // 通过调用 object() 函数创建一个新对象
clone.sayHi = function(){ // 以某种方式来增强对象
alert("hi");
};
return clone; // 返回这个对象
}
缺点
- 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
6、寄生组合式继承
function inheritPrototype (Sub, Super) {
// Object.create 可以将subPrototype的原型(__proto__) 设置成 Super的原型对象(prototype);
var subPrototype = Object.create(Super.prototype)
// 因为Object.prototype.constructor会返回对象本身,所以这里设置为Sub本身;
subPrototype.constructor = Sub
// 这里将Sub的原型对象(prototype)设置成subPrototype,实现Sub对Super基于原型链的继承;
Sub.prototype = subPrototype
}
function Super (name) {
this.name = name
}
Super.prototype.show = function () {
console.log(this.name)
}
function Sub () {
// 这里 Sub 将自己的上下文传给Super并执行,真可谓一个懒字了得;
Super.apply(this, arguments)
}
// 调用寄生组合式继承;
inheritPrototype(Sub, Super)
var instance = new Sub('我是Ammy')
// instance 本身并没有show方法,
// 但是通过inheritPrototype函数把instance对象的原型的原型设置成了Super的原型对象,
// 所以,基于JavaScript 原型链的特性,实际上是这样调用的 instance.__proto__.__proto__.show();
// 打印一下便知道了;
instance.show()
console.log(instance.__proto__.__proto__ === Super.prototype)
}
7、語法糖 ES6 Class extends继承
class People {
constructor (name) {
this.name = name
}
say () {
console.log('我是一个', this.name)
}
}
class Women extends People {
constructor (age, name) {
super(name)
this.age = age
}
}
}
函数基础 与 类继承区别
- 函数声明会提升,类声明不会。
- ES5继承和ES6继承的区别
-
ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.call(this)).
-
ES6的继承有所不同,实质上是先创建父类的实例对象this,然后再用子类的构造函数修改this。因为子类没有自己的this对象,所以必须先调用父类的super()方法,否则新建实例报错。
new 运算符
var obj = {};
obj.__proto__ = F.prototype;
F.call(obj);
以 new 操作符调用构造函数的时候,函数内部实际上发生以下变化:
- 创建了一个空对象
- 空对象的proto 指向 该函数的原型prototype。
- 函数对象的this指向该对象。
- 返回 该对象