JS进阶:原型链

引入

什么是原型链?
任何对象都有一个__proto__属性,而该属性的指针指向上一级对象(原型)最顶级的指向为Object对象。通过__proto__链接起来的关系链即为原型链。
为什么会有原型链机制?
因为JS不是传统意义上的面向对象语言,通过引入原型链机制,可以实现面向对象的机制。面向对象的核心是类,引入原型链就是类的模拟。

原型链的理解

new关键字的作用
function fun(){
	console.log("I'm a function")
	return 0;
}
var a = fun();
var b = new fun();		// 关键字new有什么作用?

如果没有new,函数执行内存图如下

  1. 在声明函数时,fun函数名入栈,堆中存放函数对象;
  2. 声明变量a入栈;
  3. 函数执行时,栈中开辟函数栈执行;
  4. 返回值为0,赋值给a。


new 使用的函数内存图

  1. 绑定this为空对象;
  2. 让空对象 [[Prototype]] 为函数的prototype属性;
    1. 所有的对象都有[[Prototype]]隐式属性,也作(proto);
    2. 所有的对象都是new出来的;
    3. [[Prototype]]无法直接通过访问符访问;
    4. 函数有显式属性prototype,可以直接访问;
  3. 执行函数;
  4. 返回值;
    1. 如果return的是基本类型,那么返回的是this的值(即this所指对象的引用);
    2. return非基本类型,那么返回原函数值,总之返回的总是一个对象类型的值。

[[GET]]的作用

访问对象属性,底层调用了[[GET]]

var a = new Person();
console.log(a.b);
a.b = 1;

以上述代码为例,底层[[GET]]的访问实现是

  1. 判断对象中是否有b这一属性;
  2. 判断该对象__proto__指向的对象里面是否有b这一属性(原型链中是否有b这个属性);
  3. 直到原型链到终点null;
  4. 若找不到,返回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

内存图分析

  1. 明确一点,所有的prototype属性都是对象,原型都指向Object.prototype;
  2. Object是由构造函数创建的,所以原型指向Function,以Object的prototype的原型为null;
  3. new的作用是构造了__proto__指向new对象的prototype的对象,并返回对象引用;
  4. 找原型链沿着__proto__所指的位置遍历到null为止;
  5. 特别注意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 的实例
底层如何运作?

  1. 判断s1.__proto__ === Object.prototype结果是 false,因为s1是由new Student得到的实例,即s1.__proto__ = Student.prototype
  2. 判断断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

内存模型

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值