zoukankan      html  css  js  c++  java
  • 吊打前端专栏 | 吊打JavaScript之从原型到原型链

    点击蓝色 “达达前端” 关注我哦!

    加个 “星标” ,每天一篇文章,一起学编程

    目录

    说一说原型模式

    每个函数都有一个 prototype 原型属性,这个属性它是一个指针,指向一个对象,而这个对象的用途是可以由特定类型的所有实例共享的属性和方法。则这个 prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。

    原型对象的好处就是让所有对象实例共享它所包含的属性和方法。

    由上述代码可知,构造函数为一个空函数,sayName()方法和所有属性直接添加到了Person的prototype属性中。

    调用构造函数来创建新对象,这个新对象会具有相同的属性和方法。但是和构造函数不同的是,这个新对象的属性和方法是所有实例共享的。

    就是,person1和person2访问的都是同一组属性和同一个sayName()函数。

    原型对象

    只要创建一个新函数,其内部就会创建一个 prototype 属性,这个属性指向函数的原型对象。而所有原型对象都会自动获得一个 constructor

    构造函数 属性,这个属性指向prototype属性所在函数的指针。

    示例:Person.protype.constructor指向Person。

    JavaScript对象原型

    所有JavaScript对象都从原型继承属性和方法。学习如何使用对象构造器。

    您现在无法对已有的对象构造器添加新属性,如果要向构造器添加一个新属性,就要把它添加到构造函数里。

    原型继承

    所有JavaScript对象都从原型继承属性和方法。日期对象继承来自Date.prototype。数组对象继承来自Arrray.prototype。

    Person对象继承来自Person.prototype。Object.prototype位于原型继承链的顶端。

    日期对象,数组对象和Person对象都继承来自Object.prototype。

    向对象添加属性和方法

    需求一,当需要向所有给定类型的已有对象添加新属性,或者是方法。

    需求二,当需要向对象构造器添加新属性,或者是是方法。

    使用prototype属性

    JavaScript中的prototype属性允许你为对象构造器添加新属性,或者是方法。

    创建了构造函数后,其原型对象会取得 constructor属性,至于其他方法,都是从Object继承来的,当调用构造函数创建一个新实例后,该实例的内部包含一个指针,指向构造函数的原型对象。

    这个指针就叫prototype,每个对象上都有一个属性叫 __proto__。注意的是这个指针存在于实例与构造函数的原型对象之间,不是存在于实例与构造函数之间。

    你可以把Person构造函数,Person的原型属性,和Person的两个实例之间的关系结构画出来分析分析。

    一个Person构造函数有一个prototype。指向了原型对象,Person Prototype。

    一个Person Prototype中有constructor,name,age,job,sayName。而Person.prototype.constructor又指回了Person。

    原型对象中除了包含一个constructor属性外,还有后来添加的其他属性。

    Person的每个实例都有一个内部属性,该属性仅仅指向了Person.prototype,严格说,它们和构造函数没有直接关系。

    重点之一,当调用构造函数创建一个新实例后,该实例的内部将包含一个指针,指向构造函数的原型对象,这个指针叫[[Prototype]]。在每个对象上都支持一个属性__proto__。

    讲解概念

    什么是对象?对象就是属性和方法的集合,即变量和函数的封装,每个对象都有一个__proto__属性,指向这个对象的构造函数的原型对象。

    什么是构造函数?构造函数就是用于创建对象的函数,通过new关键字的方法生成对象,函数名一般首字母大写。

    什么是原型对象?每个函数都有一个prototype属性,它是一个指向原型对象的指针,原型对象在定义函数时同时被创建。

    对象中的__proto__属性

    对象中的__proto__属性在所有实现中都是无法访问到的,但是可以通过 isPrototypeOf()方法来确定对象之间是否存在这种关系。

    person1和person2中内部有一个指向Person.prototype的指针,返回就是true了。

    在ECMAScript5中增加了一个新的方法,叫

    Object.getPrototypeOf(),在所有支持的实现中,这个方法返回[[Prototype]]的值。

    所以有示例如下:

    确认Object.getPrototypeOf()返回的对象实际是这对象的原型,使用Object.getPrototypeOf()可以方便地取得一个对象的原型。

    代码调用过程,调用对象的某个属性时,会首先搜索从对象实例本身开始,如果找到了给定名字的属性,则返回该属性的值,如果没有找到。

    会第二次搜索,从指针指向的原型对象开始,在原型对象中查找给定名字的属性,如果在原型对象中查找具有给定名字的属性,就返回该属性值。

    简单来说,解析器会有两问,第一次找到就一问。

    第一问:实例person1有sayName属性吗?第二问:实例person1的原型有sayName属性吗?

    虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。

    注意:person1的name被一个新值给屏蔽了。

    person1.name返回的值来自对象实例;person2.name的取值来自原型。访问person1.name时,在实例上搜索这个名为name的属性,存在,则返回其值。

    访问person2.name时,在实例上没有该属性,就会在原型上搜索,如果有该name属性,则返回其值。

    为对象添加一个属性,这个属性会屏蔽掉原型对象中相同的属性名。添加一个属性,只会阻止我们访问原型对象中的那个属性,不会改变那个属性,而是访问实例对象上的属性。

    接下来使用delete操作符删除实例属性试试,看看能否重新访问到原型中的属性。

    使用delete操作符,把保存的值删除了,恢复了对原型中的name属性的连接。

    如何判断一个属性是否存在于实例中呢,还是存在于原型中呢?

    我们可以使用hasOwnProperty()方法来给指定属性判断是否存在于对象实例中,存在对象实例中时,返回值为true。

    由以上代码可以知道,通过使用hasOwnProperty()方法,我们知道什么时候访问的是实例属性,什么时候访问的是原型属性。

    使用in操作符

    in操作符用来判断在通过对象能够访问给定属性时,返回为true。无论是该属性是在实例中还是在原型中。

    同时使用hasOwnProperty()方法和In操作符,就可以确定该属性到底是存在于对象中,还是存在于原型中。

    上述代码用来判断属性存在于原型中。

    描述,in操作符通过对象能够访问到属性就返回true,hasOwnProperty()只要属性存在于实例中时才返回true。

    所以只要in操作符返回true而hasOwnProperty()返回false,就可以确定属性是原型中的属性。

    要取得对象上所有可枚举的实例属性,可使用Object.keys()方法,这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

    可以看出,Object.keys()方法保存的是一个数组,循环顺序的出现,如果有Person的实例调用,那么出现的实例中的属性。

    如果要出现所有实例属性,无论它是否可以枚举,都可以使用

    Object.getOwnPropertyNames()方法。

    返回结果中包含不可枚举的constructor属性。

    使用这个方法创建,以对象字面量创建新对象的形式,缺少了constructor属性的指向,不再指向Person了,每个函数新创建,就会同时创建一个prototype对象,这个对象会自动获得一个constructor属性。

    此时constructor属性变成了新对象的constructor属性,指向的是Object构造函数,不再指向Person函数。

    如果想要保留constructor的指向,可以这样表示。

    这样做,constructor属性会导致[[Enumerable]]的特性为true,默认情况下,原生的constructor属性是不可以枚举的。

    重新设置构造函数,让constructor保持不可枚举。

    上述代码就可以了,只适用于ESMAScript5兼容的浏览器。

    friend.sayHi()调用可以进行访问。原因是实例与原型之间的松散连接的关系。实例与原型之间的连接只不过是一个指针,而不是一个副本,所以可以在原型中找到该想要的属性并返回保存在那里的函数。

    由上述代码,第一,先创建了一个Person的构造函数,空函数,第二,创建了一个Person的实例,第三,重写了其原型对象,调用friend.sayName()函数时发生了错误。

    因为friend的指向是Object的构造函数,而不是再指向Person函数。

    原型模式的重要性

    可以在Array.prototype中找到sort()方法,在Sting.prototype中可以找到substring()方法。

    基本包装类型String

    上述代码不建议使用该操作。

    引用类型值的属性

    使用构造函数模式和原型模式

    构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。

    有上述代码可以知道,在构造函数中定义实例属性,在原型中定义所有实例共享的属性constructor和方法sayName()。

    在person1中添加内容,并不会影响到person2,因为它们分别引用了不同的数组。

    定义应用类型或者是创建自定义类型的方式,使用组合的构造函数模式和原型模式。

    上述代码表示,只有sayName()方法不存在的情况下,才会将它添加到原型中。

    寄生构造函数模式

    继承,关于继承,有两种继承方式,接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。

    原型Person.prototype,中constructor属性:

    Person.prototype.constructor === Person,

    在__proto__属性中:

    Person.prototype.__proto__ == Object.prototype

    构造函数,prototype属性指向其原型对象,constructor属性指向Function。

    Person.constructor === Function

    __proto__属性:

    Person.__proto__ === Function.prototype

    通过字面量方式创建对象时,它的原型就是Object.prototype,我们无法直接访问内置属性[[Prototype]],但是我们可以通过

    Object.getPrototypeOf()或者是对象__proto__获取对象的原型。

    创建对象的方式是使用Object.create()。这个方法会以你传入的对象作为创建出来的对象的原型。

    什么是原型链?

    由于__proto__是如何对象都有的属性,而js里万物皆对象,所以会形成一条__proto__连起来的链条,递归访问__proto__必须最终到头,并且值为null。

    当js引擎查找对象的属性时,先查找对象本身是否存在该属性,如果不存在,会在原型链上查找,但不会查找自身的prototype。

    继承的主要思路是利用原型链,原型链作为实现继承的主要方法,利用原型让一个引用类型继承另一个引用类型的属性和方法。

    就是通过原型对象的constructor指向构造函数,实例对象通过__proto__指向原型对象。

    一层层地上溯,所有对象的原型最终都可以上溯到

    Object.prototype,即Object构造函数的prototype属性。就是所有对象都继承了Object.prototype属性。

    那么Object.prototype对象有没有它的原型呢?是有的,Object.prototype的原型就是Null。由于Null没有任何属性,就是原型链的尽头。

    读取对象中的某个属性,JavaScript引擎先寻找对象本身的属性,如果找不到就到它的原型去找,如果还是找不到,就到原型的原型中去找。直到最顶层的Object.prototype时,还是找不到,则返回Undefined。

    我做了一个示意图如下:

    当一个对象调用自身不存在的属性或者是方法时,就会去自己[proto]关联的前辈[prototype]对象上去找,如果没有找到,就会去该prototype原型[proto]关联的前辈[prototype]上去找。

    以此类推,直到找到属性或者是方法或者是Undefined为止,这就是所谓的原型链。

    总结:

    1.  每创建一个函数,该函数都会自动带有一个prototype属性,这个属性是一个指针,指向一个对象,该对象称为原型对象。

    2. 原型对象上默认有一个属性为constructor,该属性也是一个指针,指向其相关联的构造函数。

    3. 通过调用构造函数产生的实例对象,都有一个内部属性,指向了原型对象,其实例对象能够访问原型对象上的所有属性和方法。

    4. 每个构造函数都有一个原型对象,原型对象上包含着一个指向构造函数的指针,而实例都包含着一个指向原型对象的内部指针。

    5. 实例可以通过内部指针访问到原型对象,原型对象可以通过constructor找到构造函数。

    6. functions中有prototype,objects中有__proto__,prototype是函数才有的属性,__proto__是每个对象都有的属性,但__proto__不是一个规范属性,只是部分浏览器实现了此属性,对应的标准是[[Prototype]]

    7. 大多数情况下,__proto__可以理解为“构造器的原型”,即:__proto__===constructor.prototype。

    8. __proto__的指向取决于对象创建时的实现方式。

    9. 构造函数实例,封装的函数,如果通过new操作符来调用,就是构造函数,如果没有通过new操作符来调用的,就是普通函数。

    10. 函数Person(对象)有一个属性prototype指针,指向原型对象,Person.prototype原型对象,实质也是对象,它有个属性constructor指针,又指向Person函数对象。

    11. 实例对象person1有个属性[[Prototype]](内部属性,chrome和firefix,Safari中这个属性叫__proto__)指向原型对象。

    12. 可以通过实例对象的constructor访问构造函数,可以使用代码person1.constructor访问构造函数,但是constructor的本质是原型对象上的属性。

    13. 所有对象都有valueOf和toString方法的原因就是从Object.prototype继承的。

    14. 在整个原型链上寻找某个属性,对性能有影响的,越是上层的原型对象,对性能的影响就越大,如果寻找某个不存在的属性,将会遍历整个原型链。

    15. js中一切都是对象,但是也区分普通对象和函数对象,通过new Function()出来的就是函数对象。普通对象的构造函数就是Object,而函数对象的构造函数就是Function。

    16. Function prototype是一个空函数。

    17. 每个对象都有__proto__属性,但是只有函数对象才有prototype属性。

    ☆ END ☆

    参考文档来源:《JavaScript 高级程序设计》

    加群前端交流群

    扫码,备注 加群-技术领域-城市-姓名 

    目前文章内容涉及前端知识点,囊括Vue、JavaScript、数据结构与算法、实战演练、Node全栈一线技术,紧跟业界发展步伐,将 Web前端领域、网络原理等通俗易懂的呈现给小伙伴。更多内容请到达达前端网站进行学习:www.dadaqianduan.cn

    1、你知道多少this,new,bind,call,apply?那我告诉你

    2、为什么学习JavaScript设计模式,因为它是核心

    3、一篇文章把你带入到JavaScript中的闭包与高级函数

    4、大厂HR面试ES6中的深入浅出面试题知识点

    觉得本文对你有帮助?请分享给更多人

    关注「达达前端」加星标,提升前端技能

    这是一个有质量,有态度的公众号

  • 相关阅读:
    (ZOJ 3329) One Person Game (概率DP)
    python爬虫之json数据处理
    1034 Head of a Gang 图的遍历,map使用
    1030 Travel Plan Dijkstra+dfs
    vs C++ scanf 不安全
    1021. Deepest Root DFS 求最长无环路径
    1013. Battle Over Cities 用dfs计算联通分量
    无法解析的外部符号
    PAT DFS,BFS,Dijkstra 题号
    1004 Counting Leaves 对于树的存储方式的回顾
  • 原文地址:https://www.cnblogs.com/dashucoding/p/12633841.html
Copyright © 2011-2022 走看看