深刻浅出JS原型链

前言

原型链做为js中的一座大山,算是面试过程当中的必问之一;理解好原型链,对于学习js也有很大的帮助。面试

定义

咱们先来看看它的定义浏览器

当js在一个对象上查找属性的时候,首先查找对象自己的属性(即声明时候定义的属性),若是在这些属性中没有找到目标属性,那么js会找它的 __proto__对象,若是这个对象没有,那么继续找 __proto__对象的 __proto__对象,直到 __proto__null或者找到目标属性为止...... 这个__proto__的关系链,就是咱们说的原型链

特性

其实原型链真的很简单,你只要记住下面这三个点就完了。函数

  • 每个对象都有__proto__属性
  • 每个函数都有prototype属性,这个属性值也是一个对象
  • 每个对象的__protp__都会指向它的构造函数的prototype

实践

俗话说实践出真知,咱们一步步来验证这几个特性.学习

验证第一点

你们能够分段复制个人代码粘贴到浏览器控制台中this

let obj = {name:"leelei"};
console.log(obj.__proto__);

let arr = [1,2,3]
console.log(arr.__proto__);

let str = "http://www.leelei.info";
console.log(str.__proto__);

let num = 100;
console.log(num.__proto__);

当你所有打印完之后,你会发现一个问题,诶,个人str和num不是基础类型吗?不是对象也有proto属性?prototype

这里咱们要提一下js的隐式转化规则了,当你访问基础类型的一些属性或者调用一些方法的时候,你的基础类型会被js给包装起来,就像下面这样code

str.__proto__ => new String(str).__proto__;
num.__proto__ => new Number(num).__proto__;

那么这个new Stringnew Number哪里来的噶?对象

它们都是js原生的构造函数,总共有如下几种原型链

  • Object
  • Number
  • String
  • Boolean
  • RegExp
  • Error
  • Function
  • Array
  • Date
咱们如今已经验证了第一点了,每一个对象(基础类型是经过原生构造函数转成了对象)都有__proto__属性

验证第二点

请一行一行复制如下代码到控制台中get

console.log(Object.prototype);
console.log(Number.prototype);
console.log(String.prototype);
console.log(Boolean.prototype);
console.log(RegExp.prototype);
console.log(Error.prototype);
console.log(Function.prototype);
console.log(Array.prototype);
console.log(Date.prototype);

若是咱们不是构造函数呢

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

能够看到,不管是构造函数,仍是咱们声明的函数都有prototype的属性。
区别只是原生的构造函数他们的prototype对象上已经自带了一些方法和属性。

那么综上咱们能够验证第二个特性,每个函数都有 prototype属性

验证第三点

咱们只要判断下第一步的__proto__是否指向第二步对应构造函数的prototype便可。

let obj = {name:"leelei"};
obj.__proto__ === Object.prototype; //true

let arr = [1,2,3]
arr.__proto__ === Array.prototype; //true

let str = "http://www.leelei.info";
str.__proto__ === String.prototype; //true

let num = 100;
num.__proto__ === Number.prototype; //true
结果已经显而易见了,第三个论点也可获得论证

从定义出发

咱们根据定义来感觉一下这个链条

let obj = {name:"leelei"};

console.log(obj.__proto.valueOf); //有这个方法嗷

obj.valueOf(); //{name:"leelei"};

咱们能够看到,在咱们声明obj的时候并无这个属性方法,可是咱们却能够调用,咱们打印了obj的__proto__对象,发现这里存在这个方法,因此能够调用。

这里就是定义所讲的当对象自己没有这个属性的时候,咱们会去它的__proto__对象上找

这里才只有一层噶?若是隔多几层会不会调用不了了阿?
咱们能够编写一个构造函数,而后给它的原型对象prototype添加自定义方法来试验。

function Foo(age){this.age = age};
Foo.prototype.say = function(){console.log('hello')};

let foo = new Foo(18);

根据特性2,对象的__proto__对象指向构造函数的prototype,因此这样是没什么问题的

foo.say()
=> foo.__proto__.say()  //hello
至关于=> Foo.prototype.say()  //hello

那咱们还能不能使用Object上面的方法呢?固然能够

foo.valueOf()
=> foo.__proto__.valueOf 
至关于=> Foo.prototype.valueOf 怎么没有嗷?

//第一层找不到喔,那就往上一层
//不要忘了prototype也是对象!
//因此它也有__proto__属性

==> Foo.prototype.__proto__.valueOf 
至关于==> Object.prototype.valueOf 找到啦

你可能会有点好奇为何这两个会等于?
Foo.prototype.__proto__ === Object.prototype

由于Foo.prototype是对象,Foo是函数

Foo.__proto__ === Function.prototype;

Foo.prototype.__proto__ === Object.prototype;

一点须要注意的

typeof (Function.prototype); //function

这个竟然不是对象?!惊了
根据特性2,每一个函数都有一个prototype属性。
既然Function.prototype不是对象而是方法,那么它也有prototype属性才对

Function.prototype.prototype //undefined

惊了!它竟然没有prototype属性!

莫慌,除这个以外,都是适用的。记住这个特例便可。

总结

其实原型链并不复杂,可是单纯简单记忆的话确实比较难记,咱们能够本身在控制台多捣鼓一下,按照它的定义来走,更方便咱们理解和记忆。
谢谢你们,若是有错误,请在评论区指出。
打个广告,李雷的博客

相关文章
相关标签/搜索