原型
原型
[Prototype]
JavaScript
中的对象有一个特殊的[[prototype]]
内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时[[prototype]]
属性都会被默认赋予一个空的值。
[[prototype]]
有啥用呢?当我们试图引用对象的属性就会出发[[Get]]
操作,比如myObject.a
。对于默认的[[Get]]
操作来说,第一步是检查对象本身是否有这个属性,如果有的话就使用它,如果没有就需要使用对象的原型链了。
var anOtherObject= {
a: 2
};
anOtherObject.a; // 2
我们知道,除了null
和undefined
以外都是对象。每一个对象都有自己的属性和方法。那么当我们访问一个对象的属性的时候,如果这个对象没有这个属性,引擎就会往原型链上向上查找,一个对象除了拥有自己的属性和方法,也会继承来自原型上层的父级对象的属性。所以,引擎会向上查找直至找到原型链上的对象或达到原型链的顶层。
//// 假定有一个对象 o, 其自身的属性(own properties)有 a 和 b:
// {a: 1, b: 2}
// o 的原型 o.[[Prototype]]有属性 b 和 c:
// {b: 3, c: 4}
// 最后, o.[[Prototype]].[[Prototype]] 是 null.
// 这就是原型链的末尾,即 null,
// 根据定义,null 没有[[Prototype]].
// 综上,整个原型链如下:
// {a:1, b:2} ---> {b:3, c:4} ---> null
console.log(o.a); // 1
// a是o的自身属性吗?是的,该属性的值为1
console.log(o.b); // 2
// b是o的自身属性吗?是的,该属性的值为2
// o.[[Prototype]]上还有一个'b'属性,但是它不会被访问到.这种情况称为"属性遮蔽 (property shadowing)".
console.log(o.c); // 4
// c是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有.
// c是o.[[Prototype]]的自身属性吗?是的,该属性的值为4
console.log(o.d); // undefined
// d是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有.
// d是o.[[Prototype]]的自身属性吗?不是,那看看o.[[Prototype]].[[Prototype]]上有没有.
// o.[[Prototype]].[[Prototype]]为null,停止搜索,
// 没有d属性,返回undefined
上面是一个原型链的模型,每个函数都有一个原型属性prototype
指向自己的原型,而由这个函数创建的对象也有一个_proto_
属性指向这个原型。
我们可以看到这里面有_proto_
和prototype
这两个东西。如果你有使用过chrome
的develop tools
或者vscode
进行单步调试的经验的话你会经常看到_proto_
这个字段。那么,这个到底是什么意思呢?
先看_proto_
开始说起
每一个JS
对象一定对应一个原型对象,并且从原型对象那里继承属性和方法。(重点)
Every JavaScript object has a second JavaScript object (or null, but this is rare) associated with it. This second object is known as a prototype, and the first object inherits properties from the prototype.
看下面这个例子:
var one = {x: 1};
var two = new Object();
one.__proto__ === Object.protopyte //true
two.__proto__ === Object.prototype //true
one.toSting === one.__proto__.toString //true
我们看到one
和two
都拥有一个__proto__
属性,且原型对象都是Object.prototype
,拥有对象上的属性和方法。
那么,Object.prototype
到底是个啥?我们先把这个问题往后放,先搞清楚prototype
和_proto_
有什么区别?
首先,prototype
和_proto_
的第一个区别就在于:每一个对象都会有一个_proto_
属性来标示自己所继承的原型。但是函数才会有prototype
属性。当我们创建函数的时候,JS
会为这个函数追加一个prototype
属性。当我们尝试把这个函数当成一个构造函数来调用的时候,那么JS
就会创建这个构造函数的实例,这个实例会继承构造函数prototype
的所有属性和方法。同时实例会通过_proto_
指向构造函数的prototype
。
于是JS
就是这样通过_proto_
和prototype
来实现原型链。
构造函数就是通过prototype
来保存要共享给实例的属性和方法。
对象的_proto_
总是指向自己的构造函数的prototype
Object.prototype
哪里是[[prototype]]
的尽头呢?如上图,我们顺着箭头方向可以看到所有普通的[[prototype]]
链最终都会指向内置的Object.prototype
。由于所有的普通对象都源于这个对象,所以它包含了JavaScript
中许多通用的功能。
有些功能我们已经很熟悉了,比如说.toString()
和.valueOf()
等等。
给一个对象设置属性并不仅仅是添加一个新属性或者修改已有的属性值。现在我们完整地讲解一下这个过程
myObject.foo = "bar";
如果myObject
对象中包含名为foo
的普通数据访问属性,这条赋值语句只会修改已有的属性值。
如果foo
不是直接存在于myObject
中,[[Prototype]]
链就会被遍历,类似[[Get]]
操作。如果原型链上找不到foo
,foo
就会被直接添加到myObject
上。
然而,如果foo
存在于原型链上层,赋值语句myObject.foo = "bar"
的行为就会有些不同(而且可能很出人意料)。稍后我们会进行介绍。
如果属性名foo
既出现在myObject
中也出现在myObject
的[[Prototype]]
链上层,那么就会发生屏蔽。myObject
中包含的foo
属性会屏蔽原型链上层的所有foo
属性,因为myObject.foo
总是会选择原型链中最底层的foo
属性。
屏蔽比我们想象中更加复杂。下面我们分析一下如果foo
不直接存在于myObject
中而是存在于原型链上层时myObject.foo = "bar"
会出现的三种情况。
- 如果在
[[Prototype]]
链上层存在名为foo
的普通数据访问属性(参见第 3 章)并且没有被标记为只(writable:false),那就会直接在myObject
中添加一个名为foo
的新属性,它是屏蔽属性。 - 如果在 [Prototype] 链上层存在 foo,但是它被标记为只读(writable:false),那么无法修改已有属性或者在 myObject 上创建屏蔽属性。如果运行在严格模式下,代码会抛出一个错误。否则,这条赋值语句会被忽略。总之,不会发生屏蔽。
- 如果在 [Prototype] 链上层存在 foo 并且它是一个 setter(参见第 3 章),那就一定会调用这个 setter。foo 不会被添加到(或者说屏蔽于)myObject,也不会重新定义 foo 这个 setter。
给对象添加属性大多数情况是第一种情况,但是当原型链已存在该同名属性时,我们就不能用=
来赋值了。我们可以用Object.defineProperty
来向对象添加属性。
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- ThingJS数据对接方法介绍——Ajax
- python-剑指offer41-62
- 【python-opencv】读取、显示、写入图像
- WSL——windows上的linux子系统
- 【python-opencv】读取、显示、保存视频
- 超级账本——Hyperledger Fabric
- 【python-opencv】绘图(目标检测框及其置信度等)
- 哈希表:哈希值太大了,还是得用set
- 哈希表:今天你快乐了么?
- 简单二分法查找(binary search)
- 【python-opencv】鼠标作为画笔
- npm 依赖管理中被忽略的那些细节
- 哈希表:map等候多时了
- 简单了解异步通信
- 哈希表:其实需要哈希的地方都能找到map的身影