我都说的这么直白了,你还不懂原型链???

321 阅读6分钟

前戏🤩

在传统的OOP中,首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。

JS的概念都跟传统的概念不一样呀,我们是否能暂且称为 非主流😂

而在各大书籍中,Javascript常常被描述为基于原型的语言,今天我们来康康如何去诠释原型这个概念。

基于原型的语言???

先来瞅瞅概念 😴

基于原型的语言就是每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。

概念果然就是概念,说的似懂非懂的😮

先看个例子

function Leon(){}
console.log(Leon.prototype);

把上面两行代码复制到Chrome的Console中,可以看到:

我定义了个Leon函数,这个函数就自己带了个prototype。所以说Leon的原型对象就是Leon.prototype

注意哦,这是两个对象,如下图所示:

可以看到,定义的function Leon(){}就是构造函数,该函数上的prototype指向原型对象(Leon.prototype),在原型对象中又constructor属性,该属性又指向了构造函数。 好绕啊有没有😫

那我们接着尝试,如果我们在构造函数上加上个属性试试。

Leon.company = 'ns';

可以看到它加在了原型对象(Leon.prototype)指向的构造函数(Leon())上

接着来,我们在Leon的原型对象上继续加上company属性

Leon.prototype.company = 'tencent';

可以看到他将属性加到了与constructor同级的属性上。也就是Leon.prototype中。

好啦,那这阵问题来了,当我输入Leon.company会显示ns还是tencent??

因为Leon.company这句话的意思就是明确表示我要Leon.company,同理,Leon.prototype.company就是原型对象上的company。

来new一下🥾

接下来,我们用JS来新建一个实例。

什么,你还不懂实例??? 好吧我解释一下,

几年前刚开始学Java的时候,第一堂课就说Java是一个面向对象的语言。

对于Java编程来说,首先得先创建一个类,然后实例该对象。

比如,长下面这个样子🎈

说上面的东西就是了解一个概念,什么是,什么是实例

上述Java示例中,Animal是一个类,在第十行new出来的一个新的Animal对象复制给a,此时a就是Animal的一个实例。

在Javascript中,一个函数就可以相当于Java中的类了,如果要新建一个实例,它可以是下面这个样子:

function Leon(){}
var l = new Leon()

是不是很简单,此时l称为实例对象,实例对象都有一个私有属性**(称之为_proto_)**

接着说 function Leon(){}称为构造函数,该函数指向构造函数上的原型对象**(Leon.prototype)**

记住两点:①__proto__和constructor属性是对象所独有的;② prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__和constructor属性。

Object(包含Functoin, Array, Date, RegExg, Error)这些里面都有prototype

我们验证一下上面两句话,分别打印l实例对象和Leon.prototype原型对象

可以看到确实存在!😏

然后我们分别加上属性

function Leon(){}
Leon.prototype.foo = 'bar'
var l = new Leon()
l.color = 'red'

可以看到foo属性添加到了原型对象上(Leon.prototype),color属性添加到了l实例对象上。

来康康MDN怎么说的

JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 _proto_ )指向它的构造函数的原型对象(prototype )。

是不是有点明白了

qio duo ma die,等下,我们再看一遍那个图

红框的地方是不是长的一模一样,莫非👽,他们是一个??来验证下吧

l.__proto__ === Leon.prototype

果真,就是一个📢

然后我们新建一个对象

var abc = {}

然后去看看这个

然后我们发现他有__proto__属性

之后我们把abc.__prototype__与Leon.prototype属性相等。

可以看到,Leon构造函数的内容已经全部复制到了abc中

是不是已经实现了new的操作啦!!!

更细节的new我放到了后面啦

来说原型链啦🌹

既然它们是一个,说明就是通过原型对象进行连接的。

l._proto_ 指向 Leon.prototype

而原型对象(Leon.prototype)本身就是一个对象,本着是对象就有原型对象的定义🤷‍♀️,所以它也有一个_proto_

然后,我们把__proto__显示出来康康

可以看到

红框的地方就是原型对象(Leon.prototype)的__proto__属性,它的上级可以在constructor中看到是Object.

这也就解释了function在Js中也是Object的一种。

然后我们来说说,浏览器是怎么进行属性查找的。

当我们访问l.color,浏览器会查l中是否有这个属性,如果有,则返回。

如果没有,则会在l.__proto__中查找,如果有,则返回,没有,则在l._proto_._proto_(也就是Leon.prototype._proto_)中查看是否有该属性,如果有,则返回,

如果没有,则在l._proto_._proto_.__proto__上查找,当原型链上所有的__proto__都查找完了,都没找到,则返回undefined。

以上操作像不像一个链条呀,所以称为原型链 (禁止套娃🙅‍)

MDN上这么说:

原型对象也有一个自己的原型对象( _proto_ ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

现在一步一步将如下代码复制到Console中

function Leon(){}
Leon.prototype.bar = 'bar in LeonPrototype'
Leon.bar = 'bar in Leon'
Leon.prototype
Leon.bar
Leon.prototype.bar

然后new一个新对象

var a = new Leon()
a.bar

a.bar = 'cc'
a.bar
delete a.bar
a.bar
delete a.__prototype__.bar
a.bar

如果我在Object中加入原型呢

a.__proto__.__proto__.bar = 'bar in ObjectPrototype'

可以看到bar挂到了Object的原型上

所以此时

a.bar

实现一个new😛

实现一个new有四步

  • 1.创建空对象(即{})
  • 2.链接该新创建的对象(即设置对象的__proto__)到该函数的原型对象prototype上
  • 3.将步骤1新创建的对象为this的上下文
  • 4.如果该函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),则返回新创建的对象

上面我们已经实现第1,3步了,来康康其他两步。🤞

关于Arr.prototype.slice的解释developer.mozilla.org/zh-CN/docs/…

function leonNew(ctor){
	//这句话的解释参见下文链接
	var args = Array.prototype.slice.call(arguments, 1);
	
	//1.创建空对象(即{})
	var obj = {};
    
    //2.链接该新创建的对象(即设置对象的__proto__)到该函数的原型对象prototype上
    obj.__prototype = ctor.prototype;
    
    //3.将步骤1新创建的对象为this的上下文
    var result = ctor.apply(obj,args);
    
    //4.如果该函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),则返回新创建的对象
    var isObject = typeof result === 'object' && result != null;
    var isFunction = typeof result === 'function';
    return isObject || isFunction ? result : obj;
}

关于[].slice.call(arguments, 1)的意义

参考链接:blog.csdn.net/suners88267…

更详细的new实现

segmentfault.com/a/119000002…