关于js原型链
关于原型链,我们先贴上一张图(来自某知乎大佬专栏),然后听我娓娓道来。
先来说说什么是原型?
JavaScript 中的对象有一个特殊的 [[Prototype]] 内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时 [[Prototype]] 属性都会被赋予一个非空的值。但对象的 [[Prototype]] 链接可以为空,虽然很少见。
所有普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype。
所有的函数默认都会拥有一个名为 prototype 的公有并且不可枚举的属性,它会指向另一个对象。这个对象通常被称为该函数的原型。
- function Foo () {}
- var f = new Foo()
- console.log(Foo.prototype) // {}
- Object.getPrototypeOf(f) === Foo.prototype; // true Object.getPrototypeOf()获取一个对象的[[Prototype]] 链
- //绝大多数(不是所有!)浏览器也支持一种非标准的方法来访问内部 [[Prototype]] 属性
- f.__proto__ === Foo.prototype // true
当调用new Foo()时,会创建f并给f一个内部的[[Prototype]] 链接关联到Foo.prototype指向的那个对象。
接着我们来看看“构造函数”
先看一个例子,
- function Foo () {}
- Foo.prototype.constructor === Foo; // true
- var f = new Foo();
- f.constructor === Foo; // true
从代码中,我们可以看到Foo.prototype默认的constructor属性引用的是对象所关联的函数Foo,通过构造函数调用创建的对象f也有一个constructor属性,并指向创建这个对象的函数Foo。但是f本身并没有constructor这个属性,而是一种委托。
实际上,new会劫持所有普通函数并用构造对象的形式来调用它。Foo本身并不是一个构造函数,只是进行了一次构造函数调用。
换句话说,在 JavaScript 中对于“构造函数”最准确的解释是,所有带 new 的函数调用。 函数不是构造函数,但是当且仅当使用 new 时,函数调用会变成“构造函数调用”。
- function Foo(name) {
- this.name = name;
- }
- Foo.prototype.myName = function() {
- return this.name;
- };
- var a = new Foo( "a" );
- var b = new Foo( "b" );
- a.myName(); // "a"
- b.myName(); // "b"
在创建的过程中,a 和 b 的内部 [[Prototype]] 都会关联到 Foo.prototype 上。当 a 和 b 中无法找到 myName 时,它会(通过委托)在 Foo.prototype 上找到。
在思考一下这个例子,
- function Foo() { /* .. */ }
- Foo.prototype = { /* .. */ }; // 创建一个新原型对象
- var a1 = new Foo();
- a1.constructor === Foo; // false
a1.constructor === Object; // true
当我们创建了一个新对象替换了函数默认的prototype对象引用,那么新对象便不会自动获得constructor属性了。
实际上,对象的 .constructor 会默认指向一个函数,这个函数可以通过对象的 .prototype 引用。此外,你可以给任意 [[Prototype]] 链中的任意对象添加一个名 为 constructor 的属性或者对其进行修改,你可以任意对其赋值。
原型继承的机制
例如,f可以“继承”Foo.prototype 并访 问 Foo.prototype 的 myName() 函数。
- function Foo(name) {
- this.name = name;
- }
- Foo.prototype.myName = function() {
- return this.name;
- };
- function Bar(name,label) {
- Foo.call( this, name );
- this.label = label;
- }
- // 我们创建了一个新的 Bar.prototype 对象并关联到 Foo.prototype
- Bar.prototype = Object.create( Foo.prototype );
- // 注意!现在没有 Bar.prototype.constructor 了,如果你需要这个属性的话可能需要手动修复一下它
- Bar.prototype.myLabel = function() {
- return this.label;
- };
- var a = new Bar( "a", "obj a" );
- a.myName(); // "a"
- a.myLabel(); // "obj a"
Object.create() 会创建一个“新”对象并把新对象内部的 [[Prototype]] 关联到你 指定的对象(本例中是 Foo.prototype)。
ES6 添加了辅助函数 Object.setPrototypeOf(),可以用标准并且可靠的方法来修 改关联。
- // ES6 之前需要抛弃默认的 Bar.prototype
- Bar.ptototype = Object.create( Foo.prototype );
- // ES6 开始可以直接修改现有的 Bar.prototype
- Object.setPrototypeOf( Bar.prototype, Foo.prototype );
那么我们如何寻找出对象的祖先(委托关联)呢?
1.站在“类”的角度判断
- a instanceof Foo; // true
- instanceof 操作符的左操作数是一个普通的对象,右操作数是一个函数。instanceof 回答
- 的问题是:在 a 的整条 [[Prototype]] 链中是否有指向 Foo.prototype 的对象?
2.判断[[Prototype]]反射
- Foo.prototype.isPrototypeOf( a ); // true
- isPrototypeOf(..) 回答的问题是:在 a 的整 条 [[Prototype]] 链中是否出现过 Foo.prototype ?
关于.__proto__(它不是标准,IE下没有这个属性)
它可以引用函数内部的[[Prototype]]对象, 如果你想查找原型链,可以通过.__proto__.__proto__...的方式来遍历。它和其他常用函数,如.toString(), isPrototypeOf()...一样,存在于内置的Object.prototype中。
它的大致实现是这样的,
- Object.defineProperty( Object.prototype, "__proto__", {
- get: function() {
- return Object.getPrototypeOf( this );
- },
- set: function(o) {
- // ES6 中的 setPrototypeOf(..)
- Object.setPrototypeOf( this, o );
- return o;
- }
- });
通常来说,这个链接的作用是:如果在对象上没有找到需要的属性或者方法引用,引擎就 会继续在 [[Prototype]] 关联的对象上进行查找。
同理,如果在后者中也没有找到需要的 引用就会继续查找它的 [[Prototype]],以此类推。这一系列对象的链接被称为“原型链”。一般会把对象共有的属性和方法都放在构造函数的原型对象上。
然后,我们来说说原型链的指向
1、通过字面量和 new Object()
所创建的对象,他们是构造函数是 function Object()
的实例,Object
构造函数的 prototype
指向原型对象 Object.prototype
,Object.prototype
的 constructor
指向构造函数 Object
,而实例的 __proto__
也指向 Object.prototype
,Object.prototype
的 __proto__
指向 null
,所以 Object.prototype
也叫做顶级原型对象。
2、最上面图中 new Foo()
创建的对象是构造函数 function Foo()
的实例,Foo
的 prototype
指向原型对象 Foo.prototype
,Foo.prototype
的 constructor
指向构造函数 Foo
,而实例的 __proto__
也指向 Foo.prototype
,并且 Foo.prototype
虽然是原型对象,但也是对象,所以是构造函数 Object
的实例,__proto__
指向顶级原型对象 Object.prototype
。
3、数组的构造函数是 function Array()
原型链的指向与其他除 Object
以外的构造函数相同,Array.prototype
的 __proto__
也指向顶级原型对象 Object.prototype
,每一个数组都是 Array
的实例,__proto__
都指向 Array.prototype
。
4、Object
、Array
、Foo
等构造函数的本质也是对象,他们的构造函数是 function Function()
,Function
的 prototype
指向 Function.prototype
,Function.prototype
的 constructor
指向 Function
,所有的构造函数的 __proto__
都指向 Function.prototype
,包括 Function
本身,也就是说构造函数 Function
是由自己构造的,Function.prototype
的 __proto__
同样指向顶级原型对象 Object.prototype
。
下面是题外话:
关于Object.create()
Object.create(..) 会创建一个新对象并把它关联到我们指定的对象,这样 我们就可以充分发挥 [[Prototype]] 机制的威力(委托)并且避免不必要的麻烦(比如使 用 new 的构造函数调用会生成 .prototype 和 .constructor 引用)。
注意:Object.create(null) 会 创 建 一 个 拥 有 空( 或 者 说 null)[[Prototype]] 链接的对象,这个对象无法进行委托。由于这个对象没有原型链,所以 instanceof 操作符(之前解释过)无法进行判断,因此总是会返回 false。 这些特殊的空 [[Prototype]] 对象通常被称作“字典”,它们完全不会受到原 型链的干扰,因此非常适合用来存储数据。
Object.create()的polyfill代码:
- if (!Object.create) {
- Object.create = function(o) {
- function F(){}
- F.prototype = o;
- return new F();
- };
- }
参考: 《你不知道的javascript 上卷》
关于js原型链的更多相关文章
- JS原型链
JS作为发展了多年了对象语言,支持继承,和完全面向对象语言不同的是,JS依赖原型链来实现对象的继承. 首先JS的对象分两大类,函数对象和普通对象,每个对象均内置__proto__属性,在不人为赋值__ ...
- 深入分析JS原型链以及为什么不能在原型链上使用对象
在刚刚接触JS原型链的时候都会接触到一个熟悉的名词:prototype:如果你曾经深入过prototype,你会接触到另一个名词:__proto__(注意:两边各有两条下划线,不是一条).以下将会围绕 ...
- js原型链与继承(初体验)
js原型链与继承是js中的重点,所以我们通过以下三个例子来进行详细的讲解. 首先定义一个对象obj,该对象的原型为obj._proto_,我们可以用ES5中的getPrototypeOf这一方法来查询 ...
- JS 原型链图形详解
JS原型链 这篇文章是「深入ECMA-262-3」系列的一个概览和摘要.每个部分都包含了对应章节的链接,所以你可以阅读它们以便对其有更深的理解. 对象 ECMAScript做为一个高度抽象的面向对象语 ...
- 深入理解JS原型链与继承
我 觉得阅读精彩的文章是提升自己最快的方法,而且我发现人在不同阶段看待同样的东西都会有不同的收获,有一天你看到一本好书或者好的文章,请记得收藏起来, 隔断时间再去看看,我想应该会有很大的收获.其实今天 ...
- js 原型链和继承(转)
在理解继承之前,需要知道 js 的三个东西: 什么是 JS 原型链 this 的值到底是什么 JS 的 new 到底是干什么的 1. 什么是 JS 原型链? 我们知道 JS 有对象,比如 var ob ...
- 一张图看懂 JS 原型链
JS 原型链,画了张图,终于理清楚各种关系有木有 写在最后: __proto__是每个对象都有的一个属性,而prototype是函数才会有的属性!!! function Person() { } 是函 ...
- 简单粗暴地理解js原型链–js面向对象编程
简单粗暴地理解js原型链–js面向对象编程 作者:茄果 链接:http://www.cnblogs.com/qieguo/archive/2016/05/03/5451626.html 原型链理解起来 ...
- JS原型链与继承别再被问倒了
原文:详解JS原型链与继承 摘自JavaScript高级程序设计: 继承是OO语言中的一个最为人津津乐道的概念.许多OO语言都支持两种继承方式: 接口继承 和 实现继承 .接口继承只继承方法签名,而实 ...
- 02 js原型链
1 js原型链是一个绕不开的话题.直接上说吧. /** * 1. js里的原型链是怎么样的? 带class 和不带class的原型链的不同. */ const util = require('util ...
随机推荐
- C# 控制台运行 应用运行
https://blog.csdn.net/Koala_Ivy/article/details/79577830 开发遇到的问题 记录一下 前段时间捣鼓dotnetty框架,服务端写了一个控制台程序来 ...
- (转)Understanding Memory in Deep Learning Systems: The Neuroscience, Psychology and Technology Perspectives
Understanding Memory in Deep Learning Systems: The Neuroscience, Psychology and Technology Perspecti ...
- 用yarn代替cnpm,cnpm漏包有点严重
npm 的方式 npm install -g yarn 安装完成后,你可以测试下自己的版本 yarn --version 开始使用 单独安装包的方式add 不是install,后面不用加 ...
- 【转载】常用 Java 静态代码分析工具的分析与比较
摘自:http://www.oschina.net/question/129540_23043常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基本概念及主要技术,随后 ...
- Windows下Apache服务器搭建
Apache HTTP Server(简称Apache)是Apache软件基金会的一个开放源码的网页服务器,是世界使用排名第一的Web服务器软件,可以在大多数计算机操作系统中运行,由于其多平台和安全性 ...
- tkinter拦截关闭事件
import tkinter as tk from tkinter import messagebox root = tk.Tk() def on_closing(): if messagebox.a ...
- 2018 AICCSA Programming Contest
2018 AICCSA Programming Contest A Tree Game B Rectangles 思路:如果存在大于0的交面积的话, 那么肯定能找到一条水平的直线 和 一条垂直的直线, ...
- Codeforces 1005 E2 - Median on Segments (General Case Edition)
E2 - Median on Segments (General Case Edition) 思路: 首先我们计算出solve(m):中位数大于等于m的方案数,那么最后答案就是solve(m) - s ...
- echart 圆滑初始化化
圆滑:主题下载对应主题js引入后注入对应名称参数方可使用主题 初始化:tab点击的时候初始化图表涉及到tab切换到的需要延迟加载否则默认宽度为100px 1.创建macarons.js文件 2.页面添 ...
- 配置java环境jdk
最近尝试改公司的项目中的一个后台管理系统,前后台都让我一个做,所以要配置一下java环境: 1. 按装jdk 1.6//2. 安装eclipse3. 安装maven4. 安装eclispe的maven ...