作者:苏墨橘
链接:https://www.zhihu.com/question/34183746/answer/59043879
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

正好这段时间在重新看这部分,写一篇回答来梳理一下吧。

__proto__(隐式原型)与prototype(显式原型)

1.是什么

  • 显式原型 explicit prototype property:

每一个函数在创建之后都会拥有一个名为prototype的属性,这个属性指向函数的原型对象。
Note:通过Function.prototype.bind方法构造出来的函数是个例外,它没有prototype属性。(感谢

同学的答案让我知道这一点)

NOTE Function objects created using Function.prototype.bind do not have a prototype property or the [[Code]], [[FormalParameters]], and [[Scope]] internal properties. ----- ECMAScript Language Specification
  • 隐式原型 implicit prototype link:

JavaScript中任意对象都有一个内置属性[[prototype]],在ES5之前没有标准的方法访问这个内置属性,但是大多数浏览器都支持通过__proto__来访问。ES5中有了对于这个内置属性标准的Get方法Object.getPrototypeOf().
Note: Object.prototype 这个对象是个例外,它的__proto__值为null

  • 二者的关系:

隐式原型指向创建这个对象的函数(constructor)的prototype

2. 作用是什么

  • 显式原型的作用:用来实现基于原型的继承与属性的共享。
ECMAScript does not use classes such as those in C++, Smalltalk, or Java. Instead objects may be created in various ways including via a literal notation or via constructors which create objects and then execute code that initialises all or part of them by assigning initial values to their properties. Each constructor is a function that has a property named “prototype” that is used to implement prototype-based inheritance and shared properties.Objects are created by using constructors in new expressions; for example, new Date(2009,11) creates a new Date object. ----ECMAScript Language Specification
  • 隐式原型的作用:构成原型链,同样用于实现基于原型的继承。举个例子,当我们访问obj这个对象中的x属性时,如果在obj中找不到,那么就会沿着__proto__依次查找。
Every object created by a constructor has an implicit reference (called the object’s prototype) to the value of its constructor’s “prototype” ----ECMAScript Language Specification

3. __proto__的指向
__proto__的指向到底如何判断呢?根据ECMA定义 'to the value of its constructor’s "prototype" ' ----指向创建这个对象的函数的显式原型。所以关键的点在于找到创建这个对象的构造函数,接下来就来看一下JS中对象被创建的方式,一眼看过去似乎有三种方式:(1)对象字面量的方式 (2)new 的方式 (3)ES5中的Object.create() 但是我认为本质上只有一种方式,也就是通过new来创建。为什么这么说呢,首先字面量的方式是一种为了开发人员更方便创建对象的一个语法糖,本质就是 var o = new Object(); o.xx = xx;o.yy=yy; 再来看看Object.create(),这是ES5中新增的方法,在这之前这被称为原型式继承,

道格拉斯在2006年写了一篇文章,题为 Prototypal Inheritance In JavaScript。在这篇文章中,他介绍了一种实现继承的方法,这种方法并没有使用严格意义上的构造函数。他的想法是借助原型可以基于已有的对象创建新对象,同时还不比因此创建自定义类型,为了达到这个目的,他给出了如下函数:

function object(o){
function F(){}
F.prototype = o;
return new F()
}
----- 《JavaScript高级程序设计》P169

所以从实现代码 return new F() 中我们可以看到,这依然是通过new来创建的。不同之处在于由 Object.create() 创建出来的对象没有构造函数,看到这里你是不是要问,没有构造函数我怎么知道它的__proto__指向哪里呢,其实这里说它没有构造函数是指在 Object.create() 函数外部我们不能访问到它的构造函数,然而在函数内部实现中是有的,它短暂地存在了那么一会儿。假设我们现在就在函数内部,可以看到对象的构造函数是F, 现在

//以下是用于验证的伪代码
var f = new F();
//于是有
f.__proto__ === F.prototype //true
//又因为
F.prototype === o;//true
//所以
f.__proto__ === o;

因此由Object.create(o)创建出来的对象它的隐式原型指向o。好了,对象的创建方式分析完了,现在你应该能够判断一个对象的__proto__指向谁了。

好吧,还是举一些一眼看过去比较疑惑的例子来巩固一下。

  • 构造函数的显示原型的隐式原型:
  1. 内建对象(built-in object):比如Array(),Array.prototype.__proto__指向什么?Array.prototype也是一个对象,对象就是由 Object() 这个构造函数创建的,因此Array.prototype.__proto__ === Object.prototype //true,或者也可以这么理解,所有的内建对象都是由Object()创建而来。
  • 自定义对象

1. 默认情况下:

function Foo(){}
var foo = new Foo()
Foo.prototype.__proto__ === Object.prototype //true 理由同上

2. 其他情况:
(1)

 function Bar(){}
//这时我们想让Foo继承Bar
Foo.prototype = new Bar()
Foo.prototype.__proto__ === Bar.prototype //true

(2)

//我们不想让Foo继承谁,但是我们要自己重新定义Foo.prototype
Foo.prototype = {
a:10,
b:-10
}
//这种方式就是用了对象字面量的方式来创建一个对象,根据前文所述
Foo.prototype.__proto__ === Object.prototype

: 以上两种情况都等于完全重写了Foo.prototype,所以Foo.prototype.constructor也跟着改变了,于是乎constructor这个属性和原来的构造函数Foo()也就切断了联系。

  • 构造函数的隐式原型

既然是构造函数那么它就是Function()的实例,因此也就指向Function.prototype,比如 Object.__proto__ === Function.prototype

4. instanceof
instanceof 操作符的内部实现机制和隐式原型、显式原型有直接的关系。instanceof的左值一般是一个对象,右值一般是一个构造函数,用来判断左值是否是右值的实例。它的内部实现原理是这样的:

//设 L instanceof R
//通过判断
L.__proto__.__proto__ ..... === R.prototype ?
//最终返回true or false

也就是沿着L的__proto__一直寻找到原型链末端,直到等于R.prototype为止。知道了这个也就知道为什么以下这些奇怪的表达式为什么会得到相应的值了

 Function instanceof Object // true
Object instanceof Function // true
Function instanceof Function //true
Object instanceof Object // true
Number instanceof Number //false

文章参考:JavaScript instanceof 运算符深入剖析

JavaScript 隐式原型(_proto_)与显示原型(prototype)的更多相关文章

  1. JavaScript 隐式类型转换

    JavaScript 隐式类型转换 原文:https://blog.csdn.net/itcast_cn/article/details/82887895 · 1.1 隐式转换介绍 · 1.2 隐式转 ...

  2. javascript隐式原型

    上图是js原型关系图. javascript是一种基于对象的编程语言,但它与一般面向对象的编程语言不同,因为它没有class类的概念 什么是原型?? 我们每创建一个函数,它就会自带一个原型函数,这个原 ...

  3. javascript类式继承模式#4——共享原型

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  4. JavaScript 隐式转换

    javascript 中的怪癖,js运算符隐式类型转换 x + "" //等价于 String(x) + x //等价于 Number(x),也可以写成x-0 !!x //等价于 ...

  5. 每天多一点(2016.12.04)》Javascript隐式转换

    乱想 javascript为什么需要隐式转换?如果没有会出现什么情况? 找了一圈没有看到关于这个的讨论,只好自己研究了,可能不一定正确,自行辨知. 郁闷就是郁闷在好好的,为什么要搞个隐式转换,一般来讲 ...

  6. Javascript隐式转换

    乱想 javascript为什么需要隐式转换?如果没有会出现什么情况? 找了一圈没有看到关于这个的讨论,只好自己研究了,可能不一定正确,自行辨知. 郁闷就是郁闷在好好的,为什么要搞个隐式转换,一般来讲 ...

  7. javascript隐式转换详解

    Javascript是web前端开发的必学技术,今天和大家分享的就是javascript的基础知识隐式转换,希望可以帮助大家更好的学习. 转换成布尔类型假 undefined->falSe nu ...

  8. 四种JavaScript隐式类型转换的总结

    一般存在四种情况,JavaScript会对变量的数据类型进行转换. 目录 * if中的条件会被自动转为Boolean类型 * 会被转为false的数据 * 会被转为true的数据 * 参与+运算都会被 ...

  9. 有趣的JavaScript隐式类型转换

    JavaScript的数据类型是非常弱的(不然不会叫它做弱类型语言了)!在使用算术运算符时,运算符两边的数据类型可以是任意的,比如,一个字符串可以和数字相加.之所以不同的数据类型之间可以做运算,是因为 ...

随机推荐

  1. 哈希--Hash,“散列”/“哈希”

    哈希 Hash,翻译“散列”,音译为“哈希”,把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值.这种转换是一种压缩映射,也就是散列值的空间通常远小于输入的空间,不同的输入可能会散 ...

  2. [转帖]AMD三代锐龙线程撕裂者命名曝光:24核心3960X

    AMD三代锐龙线程撕裂者命名曝光:24核心3960X https://www.cnbeta.com/articles/tech/900271.htm 一直搞不懂TDP啥意思 可能会高于TDP的功率.. ...

  3. (三)spring Security 从数据库中检索用户名和密码

    文章目录 配置 Druid 数据源 数据库 Mapper 文件 自定义 `UserDetailsService` 自定义登陆校验器 `AuthenticationProvider ` 配置 secur ...

  4. Linux 实现回收站功能脚本

    #!/bin/bash function z-trash() { # 判断参数是否为空 if [ ! $1 ] then echo "z-trash error: file name of ...

  5. Selenium 调用IEDriverServer打开IE浏览器

    Selenium 调用IEDriverServer打开IE浏览器 2016年03月30日 09:49:37 标签: selenium 14836 Selenium 调用IEDriverServer打开 ...

  6. create-react-app中的一些功能配置

    1. 根路径别名@ 1. npm run eject调出配置文件.找到webpack.config.js,搜索到,alias,在下面添加一行键值对'@':paths.appSrc, alias: { ...

  7. Yii2 redis 使用方法

    /** * 基于 yii2.0 redis使用方法 *///项目根目录命令行执行 composer require --prefer-dist yiisoft/yii2-redis; //在配置文件中 ...

  8. GoLand中同一个目录下的package无法调用

    代码结构: 三个代码的package 都是 pipefilter,执行split_filter_test.go 就会提示   undefined:xxxxxxx Golang实际都可以自己补全另一个文 ...

  9. 监控SQL:执行表中所有sql语句、记录每个语句运行时间(3)

    原文:监控SQL:执行表中所有sql语句.记录每个语句运行时间(3) 通过执行一个 带参数的存储过程  exec  OpreateTB('OpreateUser','IsRun')  更新表的数据 表 ...

  10. Vue+VSCode开发环境搭建

    时间2019.9月 1. 安装 nodeJS; 安装VScode 安装好nodeJS之后,默认会安装好npm 测试 nodeJS 和npm是否可以执行 node -v npm -v 1.1 VScod ...