详细解读js的原型和原型链(新手看了也秒懂)

对原型的理解

在 JavaScript 中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性,Javascript规定,每一个函数都有一个prototype对象属性,指向函数的原型对象(原型链上面的),使用原型对象的好处是所有对象实例共享它所包含的属性和方法。

prototype(对象属性)的所有属性和方法,都会被构造函数的实例继承。这意味着,我们可以把那些不变(公用)的属性和方法,直接定义在prototype对象属性上。prototype就是调用构造函数所创建的那个实例对象的原型(proto)。

prototype可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。

1)原型是定义了一些公用的属性和方法,利用原型创建出来的新对象实例会共享原型的所有属性和方法。
实例代码:

    // 创建原型
    var Person = function(name){
        this.name = name;
    };

    // 原型的方法
   Person.prototype.sayHello = function(){
       console.log(this.name+",hello");
   };

   // 实例化创建新的原型对象,新的原型对象会共享原型的属性和方法
   var person1 = new Person("zhangsan");
   var person2 = new Person("lisi");

   // zhangsan,hello
   person1.sayHello();
   // lisi,hello
   person2.sayHello();

2)严格模式下,原型的属性和方法还是会被原型实例所共享的。
实例代码:

    // 开启严格模式,原型的属性和方法还是会被原型实例所共享的
   "use strict";

    // 创建原型
    var Person = function(name){
        this.name = name;
    };

    // 原型的方法
   Person.prototype.sayHello = function(){
       console.log(this.name+",hello");
   };

   // 实例化创建新的原型对象,新的原型对象会共享原型的属性和方法
   var person1 = new Person("zhangsan");
   var person2 = new Person("lisi");

   // zhangsan,hello
   person1.sayHello();
   // lisi,hello
   person2.sayHello();

3)通过原型创建的新对象实例是相互独立的,为新对象实例添加的方法只有该实例拥有这个方法,其它实例是没有这个方法的。
实例代码:

    // 创建原型
    var Person = function(name){
        this.name = name;
    };

    // 原型的方法
   Person.prototype.sayHello = function(){
       console.log(this.name+",hello");
   };

   // 实例化创建新的原型对象,新的原型对象会共享原型的属性和方法
   var person1 = new Person("zhangsan");
   var person2 = new Person("lisi");

   // zhangsan,hello
   person1.sayHello();
   // lisi,hello
   person2.sayHello();
   
   // 为新对象实例添加方法
   // 通过原型创建的新对象实例是相互独立的
   person1.getName = function(){
       console.log(this.name);
   }

   // zhangsan
   person1.getName();
   // Uncaught TypeError: person2.getName is not a function
   person2.getName();

4)原型的总结:
所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
所有函数都有一个prototype(原型)属性,属性值是一个普通的对象
所有引用类型的__proto__属性指向它构造函数的prototype

5)函数的原型prototype:函数才有prototype,prototype是一个对象,指向了当前构造函数的引用地址

6)函数的原型对象__proto__:所有对象都有__proto__属性, 当用构造函数实例化(new)一个对象时,会将新对象的__proto__属性指向 构造函数的prototype

7)原型对象和函数的原型的关系
6.png
说明:
所有函数的__proto__都是指向Function的prototype
构造函数new出来的对象__proto__指向构造函数的prototype
非构造函数实例化出的对象或者对象的prototype的__proto__指向Object的prototype
Object的prototype指向null

8)所有的原型对象都会自动获得一个 constructor(构造函数)属性,这个属性(是一个指针)指向 prototype 属性所在的函数(Person)

9)实例的构造函数属性(constructor)指向构造函数 :person1.constructor == Person

10)原型对象(Person.prototype)是 构造函数(Person)的一个实例

11)原型的分类:
隐式原型(proto):上面说的这个原型是JavaScript中的内置属性[[prototype]],此属性继承自object对象,在脚本中没有标准的方式访问[[prototype]],但Firefox、Safari和Chrome在每个对象上都支持一个属性_proto_,隐式原型的作用是用来构成原型链,实现基于原型的继承
显示原型(prototype):每一个函数在创建之后,便会拥有一个prototype属性,这个属性指向函数的原型对象,显示原型的作用是用来实现基于原型的继承与属性的共享

12)原型的使用方式:
通过给Calculator对象的prototype属性赋值对象字面量来设定Calculator对象的原型
在赋值原型prototype的时候使用function立即执行的表达式来赋值,可以封装私有的function,通过return的形式暴露出简单的使用名称,以达到public/private的效果

对原型链的理解

每个对象都可以有一个原型_proto_,这个原型还可以有它自己的原型,以此类推,形成一个原型链。查找特定属性的时候,我们先去这个对象里去找,如果没有的话就去它的原型对象里面去,如果还是没有的话再去向原型对象的原型对象里去寻找… 这个操作被委托在整个原型链上,这个就是我们说的原型链了。

我们知道 JS 有对象,比如

var obj = { name: 'obj' } 

我们可以对 obj 进行一些操作,包括

「读」属性
「新增」属性
「更新」属性
「删除」属性

下面我们主要来看一下「读」和「新增」属性。

为什么有 valueOf / toString 属性呢?
在我们没有对 obj 进行任何其他操作之前,发现 obj 已经有几个属性(方法)了:

1.png

那么问题来了:valueOf / toString / constructor 是怎么来?我们并没有给 obj.valueOf 赋值呀。

要搞清楚 valueOf / toString / constructor 是怎么来的,就要用到 console.dir 了。
2.png

上面这个图有点难懂,我手画一个示意图:
3.png

我们发现 console.dir(obj) 打出来的结果是:

  1. obj 本身有一个属性 name(这是我们给它加的)

  2. obj 还有一个属性叫做 proto(它是一个对象)

  3. obj.proto 有很多属性,包括 valueOf、toString、constructor 等

  4. obj.proto 其实也有一个叫做 proto 的属性(console.log 没有显示),值为 null

现在回到我们的问题:obj 为什么会拥有 valueOf / toString / constructor 这几个属性?

答案:

这跟 proto 有关。

当我们「读取」 obj.toString 时,JS 引擎会做下面的事情:

  1. 看看 obj 对象本身有没有 toString 属性。没有就走到下一步。

  2. 看看 obj.proto 对象有没有 toString 属性,发现 obj.proto 有 toString 属性,于是找到了

所以 obj.toString 实际上就是第 2 步中找到的 obj.proto.toString。

可以想象,

  1. 如果 obj.proto 没有,那么浏览器会继续查看 obj.proto.proto

  2. 如果 obj.proto.proto 也没有,那么浏览器会继续查看 obj.proto.proto.proto__

  3. 直到找到 toString 或者 proto 为 null。

上面的过程,就是「读」属性的「搜索过程」。

而这个「搜索过程」,是连着由 proto 组成的链子一直走的。

这个链子,就叫做「原型链」。

共享原型

现在我们有另一个对象

var obj2 = { name: 'obj2' }

如图:

4.png

那么 obj.toString 和 obj2.toString 其实是同一个东西,也就是 obj2.proto.toString。

这有什么意义呢?

如果我们改写 obj2.proto.toString,那么 obj.toString 其实也会变!

这样 obj 和 obj2 就是具有某些相同行为的对象,这就是意义所在。

差异化

如果我们想让 obj.toString 和 obj2.toString 的行为不同怎么做呢?

直接赋值就好了:

obj.toString = function(){ return ‘新的 toString 方法’ }

5.png

总结:

「读」属性时会沿着原型链搜索。

「新增」属性时不会去看原型链(但是如果你给这个属性加了一些配置,则不一样)。

原型链解决的是什么问题

1)原型链解决的主要是继承问题
2)每个对象拥有一个原型对象,通过 proto 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null(Object.proptotype.__proto__指向的是null)。这种关系被称为原型链(prototype chain),通过原型链一个对象可以拥有定义在其他对象中的属性和方法
3)构造函数 Parent、Parent.prototype 和 实例 p 的关系如下:(p.proto === Parent.prototype)
11.png

prototype 和 proto 的区别

1)prototype是构造函数的属性
2)__proto__是每个实例都有的属性,可以访问 [[prototype]] 属性
3)实例的__proto__与其构造函数的prototype指向的是同一个对象

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值