理解JavaScript中的__proto__和prototype

理解JavaScript中的__proto__和prototype

需要理解的一些概念

万物皆对象

虽然说 JavaScript 的面向对象不像是我们通常了解到的那些 OOP,但是,的确,在 JavaScript 中,所有的东西都是对象,这其中就包括了我们今天要说的,方法(Function)以及方法的原型(Function.prototype),他们都是对象。因此,它们都会具有对象共有的特点。
即:对象具有属性__proto__,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。

方法也就是函数(Function)

对于函数这个特殊的对象来说,除了和其他对象一样有上述__proto__属性之外,还有自己特有的属性——原型属性(prototype),这个属性是一个引用,它指向一个对象,这个被指向的对象(原型对象)包含了一些被该函数共享的属性和方法。原型对象也有一个属性,叫做 constructor,这个属性包含了一个指针,指回原构造函数,比如:

1
2
b = function() {}
b.prototype.constructor == b // true

要注意这么几点:

  1. 遵循ECMAScript标准,someObject.[[Prototype]] 符号(Symbol)是用于指向 someObject 的原型。从 ECMAScript 6 开始,[[Prototype]] 可以通过 Object.getPrototypeOf()Object.setPrototypeOf() 访问器来访问。
  1. someObject.[[Prototype]]这个等同于 JavaScript 的许多浏览器实现(非ECMAScript标准)的属性 __proto__
  1. 注意与构造函数 funcprototype 属性的区别。被构造函数创建的实例对象的 [[prototype]] 指向 funcprototype 属性。Object.prototype 属性表示 Object 的原型对象。

一张经典的图

一张经典的图

我们做几个实验来理解以下上面的这幅图

1. function foo()

1
2
function Foo(){}
Foo.prototype.constructor == Foo // true

2. new一个函数

1
2
3
function Foo(){}
let a = new Foo() // Important
a.__proto__ == Foo.prototype // true

3. 别忘了Object也是一个函数对象

1
2
3
4
5
6
typeof Object // "function"
// 和上面的1、2保持一致:

Object.prototype.constructor == Object // true
let b = new Object()
b.__proto__ == Object.prototype // true

4. 这点是图上没有的,但是我个人觉得应该需要注意一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 通常最常用的创建一个空对象
let o1 = {}
// 使用构造方法创建一个空对象
let o2 = new Object()

o1.__proto__ == Object.prototype
// true
o2.__proto__ == Object.prototype
// true

// 使用构造方法创建一个空对象
let f1 = new Function(){}
// 使用箭头函数
let f2 = () => {}
// 使用传统的语法创建一个函数
let f3 = functon(){}

f1.__proto__ == Function.prototype
// true
f2.__proto__ == Function.prototype
// true
f3.__proto__ == Function.prototype
// true

5. 下面这点图上也没有

创建函数的三种方式如下:

1
2
3
let a = function(){}
let b = Function()
let c = new Function()

bc的区别我们就不说了,其实没啥区别,但是,abc是有些区别的。他们的prototype是不一样的,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

a.prototype
// {constructor: ƒ}
// constructor: ƒ ()
// length: 0
// name: "a"
// arguments: null
// caller: null
// prototype: {constructor: ƒ}
// __proto__: ƒ ()
// [[FunctionLocation]]: VM10690:1
// [[Scopes]]: Scopes[1]
// __proto__: Object
b.prototype
// {constructor: ƒ}
// constructor: ƒ anonymous( )
// length: 0
// name: "anonymous"
// arguments: null
// caller: null
// prototype: {constructor: ƒ}
// __proto__: ƒ ()
// [[FunctionLocation]]: VM10941:1
// [[Scopes]]: Scopes[1]
// __proto__: Object
c.prototype
// {constructor: ƒ}
// constructor: ƒ anonymous( )
// length: 0
// name: "anonymous"
// arguments: null
// caller: null
// prototype: {constructor: ƒ}
// __proto__: ƒ ()
// [[FunctionLocation]]: VM10874:1
// [[Scopes]]: Scopes[1]
// __proto__: Object

看出来了么? 如果用function(){}这种方式定义的函数,他们是有一个名字的。但是其他两种方式定义出来的都是匿名函数,但是将其引用赋给了bc

最后再说一下对象的继承

当然这里包括了函数对象和普通对象。

普通对象

对于普通对象来说,我们可以使用Object.create来实现继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a = { parm_a: 'a' }
b = Object.create(a, {
parm_b: {
value: 'b',
writable: true,
configurable: true,
enumerable: true
}
})
c = Object.create(b, {
parm_c: {
value: 'c',
writable: true,
configurable: true,
enumerable: true
}
})

这样,就实现了a --> b --> c这样的继承,这样继承主要由以下特点:

  • 父对象的属性可以由子对象点出来,比如:c.parm_a
  • 如果设置了enumerable: true,那么在for ... in ...循环中,父对象的属性也可以遍历。
  • JSON.stringify中,无法对父对象的属性进行序列化。
  • c.hasOwnProperty('parm_a'),子对象对父对象的属性调用hasOwnProperty时,返回为false,父对象的属性不能用own来归类。
  • 不管继承多少层,isPrototypeOf都能找得到:a.isPrototypeOf(c) --> true

函数对象

函数对象有这样的原型链关系:Object --> Function --> Foo

与普通对象的不同,我们只说一点。函数对象中,如果父对象的prototype中定义了一个属性(这个属性可以是方法,也可以是单纯的属性),子对象中也是可以点出来的。

new一个对象的过程

在本文的最后,我们谈一个老生常谈的问题,就是new一个对象的过程。

new 关键字做了什么

  1. 创建一个新的对象,这个对象的类型是 object;
  2. 设置这个新的对象的内部、可访问性和[[prototype]]属性(这个属性就是我们上面一直在说的__proto__,他是一个非ECMAScript标准),为构造函数(指``prototype.constructor`所指向的构造函数)中设置的;
  3. 执行构造函数,将this关键字绑定到这个新创建的对象上;
  4. 返回新创建的对象(除非构造方法中返回的是‘无原型’)。

在创建新对象成功之后,如果调用一个新对象没有的属性的时候,JavaScript 会延原型链向止逐层查找对应的内容。这类似于传统的‘类继承’。

注意:在第二点中所说的有关[[prototype]]属性(__proto__),只有在一个对象被创建的时候起作用,比如使用 new 关键字、使用Object.create 、基于字面意义的(函数默认为 Function.prototype ,数字默认为 Number.prototype 等)。它只能被Object.getPrototypeOf(someObject) 所读取。没有其他任何方式来设置或读取这个值。

评论

Agile Angularjs Animation Application Artificial Intelligence BP Babel Bokeh Book C4.5 CART CD CLI CSS CentOS CheetSheet Cinder Clipboardjs Concept Continuous Delivery DeepLearning Department DevOps Develop Development Directive Distribution Django Document ECMA ELU ES5 ES6 ES7 Echarts Engine Entropy Filter Front End GELU Gallery Git Gradient descent Hexo Horizon ID3 ID3.5 Icarus JavaScript Javascript KVM LaTeX LeetCode LibreOffice Linux Logestic MNIST Machine Learning Mathematics Matrix MiddleWare Module Native Network Nginx NodeJS Numpy OOP OpenSSH OpenStack OpenStackApi Operations Oprations PDF PLA Pandas Pipline Probability Python ReLU React Relational algebra Restful Route SVD SVM Scalar Sigmoid SoftPlus Swish Team Tempest Tensor TensorFlow Testing Time TimeMachine Tips Vector Vmware Vue Vuex WSGI Web Word Cut aliyun auth babel certbot cost function debounce decision tree dns docker dockerfile eject error function footer git header homebrew html5 http https jupyter jwt keystone lab loader lodash loss function mathematics migrate nav openstack outline pdf2html pm2 proto prototype python replace request response rp rt ruby scikit-learn section singular value decomposition sklearn stylus tanh throttle url vue-router vue-ssr webpack 事件 事件代理 事件冒泡 事件捕获 位运算 低通滤波器 入门 全局 全局变量 全局对象 全栈 公式 决策树 几何意义 函数 分类器 剪枝 加速 动态变量 匹配滤波边缘检测 卷积 卷积核 原型链 双向绑定 反向传播 发布 变量类型 可视化 基尼指数 官方示例 对偶形式 对象 小技巧 平移和查分边缘检测 思维导图 感知机模型 手动实现 拉格朗日乘子法 推导 提交阶段 数据 数据绑定 最大似然估计 最小二乘估计 最小二乘回归树 最小二乘法 本地 朴素贝叶斯 朴素贝叶斯算法 机器学习 条件概率 标签模板 梯度下降 梯度方向边缘检测 概念 概率 模板字符 模板字符串 正则 求导 流程 源码 源码阅读 激活函数 灰度 特征值 特征向量 特征工程 生命周期 矩阵 神经元 神经网络 私有对象 科学计算 算法 算法实现 线性代数 线性回归 编译 缺失 联合概率 脚手架 识别 调试 贝叶斯 贝叶斯判定准则 边缘检测 边际概率 闭包 间隔 防抖动 限流 随机森林 高斯分布 高通滤波器
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×