原型和原型链
- 所有对象都是通过
new 函数
创建 - 所有的函数也是对象
- 函数中可以有属性
- 所有对象都是引用类型
原型 prototype
所有函数都有一个属性:prototype,称之为函数原型
默认情况下,prototype是一个普通的Object对象
默认情况下,prototype中有一个属性,constructor,它也是一个对 象,它指向构造函数本身。
隐式原型 proto
所有的对象都有一个属性:__proto__
,称之为隐式原型
默认情况下,隐式原型指向创建该对象的函数的原型。
当访问一个对象的成员时:
- 看该对象自身是否拥有该成员,如果有直接使用
- 在原型链中依次查找是否拥有该成员,如果有直接使用
猴子补丁:在函数原型中加入成员,以增强起对象的功能,猴子补丁会导致原型污染,使用需谨慎。
原型链
特殊点:
- Function的__proto__指向自身的prototype
- Object的prototype的__proto__指向null
链条全貌
原型链的应用
基础方法
W3C不推荐直接使用系统成员__proto__
Object.getPrototypeOf(对象)
获取对象的隐式原型
Object.prototype.isPrototypeOf(对象)
判断当前对象(this)是否在指定对象的原型链上
对象 instanceof 函数
判断函数的原型是否在对象的原型链上
Object.create(对象)
创建一个新对象,其隐式原型指向指定的对象
Object.prototype.hasOwnProperty(属性名)
Object.hasOwnProperty(属性名)
判断一个对象自身是否拥有某个属性
应用
类数组转换为真数组
Array.prototype.slice.call(类数组);
实现继承
默认情况下,所有构造函数的父类都是Object
圣杯模式
定义俩个俩个函数VIPUser,User。要使VIPUser能使用User的所有属性,实现如下图
function User(firstName, lastName, age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.fullName = this.firstName + "" + this.lastName;
}
User.prototype.sayHello = function () {
console.log(`大家好,我叫${this.fullName},今年${this.age}岁`);
}
function VIPUser(firstName, lastName, age, money) {
User.call(this, firstName, lastName, age)
this.money = money;
}
// 改变继承关系函数调用一开始就要调用,
inherit3(VIPUser, User)
VIPUser.prototype.upgrade = function () {
console.log(`使用了${100}元`)
}
var vUser = new VIPUser("我", "爱", 23, 100)
第一种
function inherit1(son, father) {
son.prototype = father.prototype;
//直接这样改会污染别人(father)的原型,所以不可取。这样并不是我们想看见的
}
第二种
function inherit2(son, father) {
var newObj = Object.create(father.prototype)
// 新创建一个对象,newObj的隐式原型__proto__指向father.prototype
son.prototype = newObj;
// 然后再把son的原型prototype指向newObj,这样就粗略的完成了继承
};
第二种细节完善版:圣杯模式标准写法
细节一:由于newObj是新创建出来的普通对象,所以他原型.prototype是没有constructor的所以我们需要手动给其添加一个,让他指向本身。
细节二,为了方便,我们一般会在son的原型son.prototype里面添加一个uber 属性,(本来是super(父亲的意思)但是由于super是保留字,所以我们用uber代替) 用来指向farther的原型father.prototype。
function inherit3(son, father) {
var newObj = Object.create(father.prototype)
// 新创建一个对象,newObj的隐式原型__proto__指向father.prototype
son.prototype = newObj;
// 然后再把son的原型prototype指向newObj,这样就粗略的完成了继承
// 细节一,添加constructor
son.prototype.constructor = VIPUser;
// 细节二,
son.uber = father.prototype;
}
圣杯模式扩展
但是在es5之后,我们已经可以通过Object.getPrototypeOf(son.prototype)方法 获取到他的隐式原型,也就是father.prototype。所以现在看来这行代码有点多余 所以我把uber指向User本身。这样操作起来就会很方便。
记得和同事沟通好
function inherit4(son, father) {
var newObj = Object.create(father.prototype)
// 新创建一个对象,newObj的隐式原型__proto__指向father.prototype
son.prototype = newObj;
// 然后再把son的原型prototype指向newObj,这样就粗略的完成了继承
// 细节一,添加constructor
son.prototype.constructor = VIPUser;
// 细节二,
// son.uber = father.prototype;
// 细节二进阶版
son.uber = father;
}
function VIPUser(firstName, lastName, age, money) {
// User.call(this, firstName, lastName, age)
// 进阶之后我们就可以不用call了,
// 直接用下面这种
this.uber(firstName, lastName, age)
this.money = money;
}
第四种,以前的写法。没有Object.create()之前
function inherit5(son, father) {
var Temp = function () {}
Temp.prototype = father.prototype;
son.prototype = new Temp();
son.prototype.constructor = son;
son.prototype.uber = father;
};
但是这样写有一个多余的东西,就是var Temp = function () {}。每次调用都会创建一个新变量
后面雅虎公司流传除了一种写法,采用闭包的方式,使var Temp = function () {};永远只运行一次。
var inherit6 = (function () {
var Temp = function () {};
return function (son, father) {
Temp.prototype = father.prototype;
son.prototype = new Temp();
son.prototype.constructor = son;
son.prototype.uber = father;
}
}());