引入
什么是原型链?
任何对象都有一个__proto__属性,而该属性的指针指向上一级对象(原型)最顶级的指向为Object对象。通过__proto__链接起来的关系链即为原型链。
为什么会有原型链机制?
因为JS不是传统意义上的面向对象语言,通过引入原型链机制,可以实现面向对象的机制。面向对象的核心是类,引入原型链就是类的模拟。
原型链的理解
new关键字的作用
function fun(){
console.log("I'm a function")
return 0;
}
var a = fun();
var b = new fun(); // 关键字new有什么作用?
如果没有new,函数执行内存图如下
- 在声明函数时,fun函数名入栈,堆中存放函数对象;
- 声明变量a入栈;
- 函数执行时,栈中开辟函数栈执行;
- 返回值为0,赋值给a。
new 使用的函数内存图
- 绑定this为空对象;
- 让空对象 [[Prototype]] 为函数的prototype属性;
- 所有的对象都有[[Prototype]]隐式属性,也作(proto);
- 所有的对象都是new出来的;
- [[Prototype]]无法直接通过访问符访问;
- 函数有显式属性prototype,可以直接访问;
- 执行函数;
- 返回值;
- 如果return的是基本类型,那么返回的是this的值(即this所指对象的引用);
- return非基本类型,那么返回原函数值,总之返回的总是一个对象类型的值。
[[GET]]的作用
访问对象属性,底层调用了[[GET]]
var a = new Person();
console.log(a.b);
a.b = 1;
以上述代码为例,底层[[GET]]的访问实现是
- 判断对象中是否有b这一属性;
- 判断该对象__proto__指向的对象里面是否有b这一属性(原型链中是否有b这个属性);
- 直到原型链到终点null;
- 若找不到,返回undefined(不会报错)。
如果b属性不存在,对其进行赋值,底层还会调用[[PUT]]操作创建属性并赋值。
应用
共享方法与变量
有用[[GET]]原型链的存在
function fun(){
console.log("a function")
}
// 在原型链上创建共享变量与函数
fun.prototype.data = "shared data"
fun.prototype.work = function(){
console.log("it's a shared function")
}
// 使用原型链上的变量与函数
var a = new fun() // 'a function'
console.log(a.data) // 'shared data'
a.work() // 'it's a shared function'
静态方法与实例方法
实例方法,定义在prototype中,实例就可以之直接使用。
静态方法,定义在函数中,需要使用函数名访问调用。
function fun(){}
fun.prototype.exampleMethod = function(){
console.log("example method")
}
fun.staticMethod = function(){
console.log("static method")
}
var a = new fun()
fun.staticMethod()
a.exampleMethod();
练习
打印结果是什么?
Object.prototype.a = function(){
console.log('a')
}
Function.prototype.b = function(){
console.log('b')
}
var F = function () {}
var f = new F()
f.a()
f.b()
F.a()
F.b()
// a
// × f.b is not a function 报错
// a
// b
内存图分析
- 明确一点,所有的prototype属性都是对象,原型都指向Object.prototype;
- Object是由构造函数创建的,所以原型指向Function,以Object的prototype的原型为null;
- new的作用是构造了__proto__指向new对象的prototype的对象,并返回对象引用;
- 找原型链沿着__proto__所指的位置遍历到null为止;
- 特别注意Function的__proto__是Function.prototype,即函数的原型是函数的原型对象。这是由JS宿主环境注入的。
Function.__proto__ = Function.prototype
如何用原型链实现面向对象?
// 模拟类
function Student(name){
this.name = name
// 公共成员方法
this.say = function(){
console.log("hello",this.name)
}
}
// 公共方法
Student.prototype.smile = function(){
console.log("hahaha",this.name)
}
// 静态方法
Student.cry = function(){
console.log("crying",this.name)
}
var s1 = new Student("LiMing")
s1.say(); // hello LiMing
s1.smile(); // hahaha LiMing
s1.cry(); // TypeError: s1.cry is not a function
Student.cry();// hello Student
拓展
instanceof
以上面 Student 类为例,对于s1 instanceof Object
语句的作用是判断 obj 是否为 Object 的实例
底层如何运作?
- 判断
s1.__proto__ === Object.prototype
结果是 false,因为s1是由new Student得到的实例,即s1.__proto__ = Student.prototype
- 判断断
s1.__proto__.__proto__ === Object.prototype
结果是 true,因为Student.prototype.__proto__ = Object.prototype
简易实现
// 编写在Object静态方法上,参数分别是实例和原型
Object.myInstanceOf = function(instance, target){
// 递归终点,直到原型链终点
if(instance === null) return false
if(Object.getPrototypeOf(instance) === target.prototype) return true
else return Object.myInstanceOf(Object.getPrototypeOf(instance), target)
// Object.getPrototypeOf(instance) --> 返回实例的隐式原型
}
数组与类数组
为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组?
本质上的区别是类数组原型是Object.prototype,而不是Array.prototype。一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。如何将类数组转换为数组呢?
const arr = Array.from(arguments)
const arr = [...arguments]
原型的接口实现继承
实例的接口
function Person(name){ this.name = name }
Person.prototype.say = function(){console.log('hello',this.name)}
var p1 = new Person("LiMing")
// 获取实例的原型接口
Object.getPrototypeOf(p1) // Person.prototype
// 设置原型接口(存在效率问题,不推荐)将obj的原型__proto__设置为相应的prototype
var p2 = {};
Object.setPrototypeOf(p2, Person.prototype)
// p2{__proto__ = Person.prototype}
// 推荐的接口,接口作用是返回一个 __proto__ 指向参数的对象。如果传入是prototype就实现了原型链
var p3 = Object.create(Person.prototype,{name: {value: "LiHua"}})
// p3{name:'LiHua', __proto__:Person.prototype}
p3.say() // hello LiHua
// Object.create的效果和new的效果是一样的
Object.getPrototypeOf(p1) === Object.getPrototypeOf(p3) // true
继承,是类的操作,注意与实例区分。
// 父类
function Person(){
this.say = function(){console.log('hello')} // 子类无法访问
}
Person.prototype.smile = function(){console.log('smile')}
// 子类
function Student(){}
Student.prototype.cry = function(){console.log('cry')}
// 如何实现继承?直接将父类的原型给注入到子类原型中即可即可
Student.prototype = Object.create(Person.prototype)
Student.prototype.__proto__ === Person.prototype // true
// 注意上述会完全覆盖Student的prototype属性,所以在继承完后再添加自己的类方法
Student.prototype.cry = function(){console.log('cry')}
var s1 = new Student();
// try{
// s1.say() // TypeError: s1.say is not a function
// }catch(e){
// console.log(e)
// throw new Error('s1.say() 不存在')
// }
s1.smile() // smile
s1.cry() // cry
内存模型