今天讲一道经典的原型链面试题。

原型链是什么

JavaScript 中,每当创建一个对象,都会给这个对象提供一个内置对象 [[Prototype]] 。这个对象就是原型对象,[[Prototype]] 的层层嵌套就形成了原型链。

当我们访问一个对象的属性时,如果自身没有,就会通过原型链向上追溯,找到第一个存在该属性原型对象,取出对应值。

当然原型链不是无止境的,和单链表一样,最后一个原型对象的值是 null,原型链的所有对象都找不到指定的属性时,我们会拿到 undefined。

[[Prototype]] 虽然无法通过脚本进行访问,但大多数浏览器提供了 __proto__ 属性来访问这个内置对象,但它并不是标准,无法兼容所有浏览器。

下面来举几个例子,让读者对原型链有一个直观的认识:

  • 通过对象字面量声明 a = {} 时, a 的 [[prototype]] 就是 Object.prototype。此时的原型链是:a -> Object.prototype -> null。这里有个易错点,就是以为 a 的上一个原型对象是 Object,其实并不对,Object 其实只是一个构造函数。
  • 声明数组 arr = [1, 2, 4],它的原型链则是 arr -> Array.prototype -> Object.prototype -> null。
  • Object.create(null) 甚至能够创建一个连 [[prototype]] 都没有的真正的空对象,一般用于做字符串哈希表,比如 vue 源码里就能经常看到。

通过构造函数创建实例对象

在 JavaScript 中,一个函数会在 new 关键字的配合下成为构造函数。也就是说,任何一个函数都可以成为构造函数。

当声明一个构造函数时,它会有一个属性名为 prototype 的对象(和 [[prototype]] 是不同的东西),这个对象就是 原型对象。这个对象的 constructor 又反过来指向构造函数。

当我们对使用 new 关键字创建对象,被创建的对象的 [[prototype]] 会指向这个 prototype。

function Rect() {}
const rect = new Rect()
rect.__proto__ === Rect.prototype // true
Rect.prototype.constructor === Rect // true

只要是通过 new Rect() 创建的对象,无论多少次,它的 [[prototype]] 都是指向 Rect.prototype。另外,Rect.prototype.prototype 指向的是 Object.prototype。

这样,通过给构造函数的原型对象(Rect.prototype)添加一些方法(如 Rect.prototype.draw),就能让创建的多个实例对象共享同一个方法,减少内存的使用。

用原型链的方式实现继承

理解了构造函数如何影响创建的实例的原型链后,我们来探讨一下核心问题,如何使用原型链来实现继承。

假设我们有一个 Shape 构造函数(父类)和 Rect 构造函数(子类)。代码如下:

// 父类
function Shape() {}
Shape.prototype.draw = function() {
  console.log('Shape Draw')
}
Shape.prototype.clear = function() {
  console.log('Shape Clear')
}
// 子类
function Rect() {}
/** 
 实现继承的代码放这里
**/
Rect.prototype.draw = function() {
  console.log('Rect Draw')
}

通过前面的学习,我们知道,正常情况下使用 new Rect 创建的实例对象,它的原型链是这样的:

rect -> Rect.prototype -> Object.protoype -> null

现在我们要实现的继承,其实就是在原型链中间再加一个原型对象 Shape.prototype。对此我们需要对 Rect.prototype 进行特殊的处理。

方法1:Object.create

Rect.prototype = Object.create(Shape.prototype)
Rect.prototype.constructor = Rect // 选用,如果要用到 constructor

Rect.prototype.constructor = Rect // 选用,如果要用到 constructor

Object.create(proto) 是个神奇的方法,它能够创建一个空对象,并设置它的 [[prototype]] 为传入的对象。

因为我们无法通过代码的方式给 [[prototype]] 属性赋值,所以使用了 Object.create 方法作为替代。

因为 Rect.prototype 指向了另一个新的对象,所以把 constructor 给丢失了,可以考虑把它放回来,如果你要用到的话。

缺点是替换掉了原来的对象。

方法2:直接修改 [[prototype]]

如果就是不想使用新对象,只想修改原对象,可以使用 废弃 的 __proto__ 属性,但不推荐。

不过另外还有一个方法 Object.setPrototypeOf() 可以修改对象的 [[prototype]],但因为性能的问题,也不推荐使用。

Object.setPrototypeOf(Rect.prototype, Shape.prototype)
// 或 
Rect.prototype.__proto__ = Shape.prototype

都不推荐使用,但确实能用。

方法3:使用父类的实例

Rect.prototype = new Shape()

形成的原型链为:

rect -> shape(替代掉原来的 Rect.prototype) -> Shape.prototype -> Object.prototype -> null

基本能用,缺点是会产生副作用,就是执行 new Shap() 可能会出现副作用,比如给创建的对象添加了一些属性、发送了请求之类的,完全取决于构造函数内的代码。

某种意义上,这个缺点是致命的。不推荐使用。

总结

用原型链的方式实现一个 JS 继承,其实就是希望构造函数 Son 创建出来的对象 son,它的原型链上加上父类 Parent.prototype,所以最后就是要修改 Son.prototype 的 [[prototype]]。

鉴于性能、兼容性、副作用等考虑,推荐使用方法 1,即通过 Object.create(Parent.prototype) 创建一个指定了 [[prototype]] 的新对象,替换掉原来的 Son.prototype 指向的对象。

总结几个核心知识点:

  • 任何对象都有 [[prototype]] 属性,读写对象属性发现当前对象不存在时,会访问 [[prototype]] 指向的对象尝试访问属性,于是原型链形成了。
  • 函数创建时,它的 prototype 属性会拿到一个原型对象。当函数作为构造函数,通过 new 创建一个新对象时,这个新对象的 [[prototype]] 会指向这个原型对象。
  • JS 要实现 “类” 继承,本质是通过处理构造函数的 prototype 对象来修改原型链。

以上就是一文详解如何用原型链的方式实现JS继承的详细内容,更多关于JS 原型链 继承的资料请关注Devmax其它相关文章!

一文详解如何用原型链的方式实现JS继承的更多相关文章

  1. html5 拖拽及用 js 实现拖拽功能的示例代码

    这篇文章主要介绍了html5 拖拽及用 js 实现拖拽,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  2. amaze ui 的使用详细教程

    这篇文章主要介绍了amaze ui 的使用详细教程,本文通过多种方法给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. swift皮筋弹动发射飞机ios源码

    这是一个款采用swift实现的皮筋弹动发射飞机游戏源码,游戏源码比较详细,大家可以研究学习一下吧。

  4. Swift与Js通过WebView交互

    开发环境:Swfit2.3XCode8.2基础概念jscontext,jscontext是代表JS的执行环境,通过-evaluateScript:方法就可以执行一JS代码JSValue,JSValue封装了JS与ObjC中的对应的类型,以及调用JS的API等JSExport,JSExport是一个协议,遵守此协议,就可以定义我们自己的协议,在协议中声明的API都会在JS中暴露出来,才能调用Swif

  5. JSCore swift

    如果双方相互引用,会造成循环引用,而导致内存泄露。以上是Jscore的基本使用,比较简单

  6. Swift WKWebView的js调用swift

    最近项目需求,需要用到JavaScriptCore和WebKit,但是网上的资源有限,而且比较杂,都是一个博客复制另外一个博客,都没有去实际敲代码验证,下面给大家分享一下我的学习过程。

  7. Swift WKWebView的swift调用js

    不多说,直接上代码:在html里面要添加的的代码,显示swift传过去的参数:这样就实现了swift给js传参数和调用!

  8. 在 Swift 專案中使用 Javascript:編寫一個將 Markdown 轉為 HTML 的編輯器

    你有強烈的好奇心,希望在你的iOS專案中使用JavaScript。jscontext中的所有值都是JSValue對象,JSValue類用於表示任意類型的JavaScript值。因此,我們既需要寫Swift代碼也要寫JavaScript代碼。此外,我們還會在JavaScript中按照這個類的定義來創建一個對象并對其屬性進行賦值。從Swift中呼叫JavaScript就如介紹中所言,JavaScriptCore中最主要的角色就是jscontext類。一個jscontext對象是位於JavaScript環境和本

  9. swift - WKWebView JS 交互

    本文介绍WKWebView怎么与js交互,至于怎么用WKWebView这里就不介绍了HTML代码APP调JS代码结果JS给APP传参数首先注册你需要监听的js方法名2.继承WKScriptMessageHandler并重写userContentController方法,在该方法里接收JS传来的参数3.结果

  10. swift 开发UIWebView跟JS的交互

    前言作为小白的我,才开始入门IOS,选择了swift来进行入门学习,学习做着公司一个简单的小小项目,该项目需要进行跟H5进行交互,然后我就开始研究了UIWebView的使用,其实基本原理跟Android的一样,因为我是Android开发的,所以就顺水推舟了。))//这里设置你需要加载的地址}overridefuncdidReceiveMemoryWarning(){super.didReceiveMemoryWarning()//disposeofanyresourcesthatcanberecreate

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部