帮助面向对象开发者理解关于JavaScript对象机制

本文是以一个熟悉OO语言的开发者视角,来解释JavaScript中的对象。

对于不了解JavaScript 语言,尤其是习惯了OO语言的开发者来说,由于语法上些许的相似会让人产生心理预期,JavaScript中的原型继承机制和class语法糖是让人迷惑的。

如果你已经对prototype机制已有了解,但是由于两者对象机制的巨大(本质)差异,对它和构造函数,实例对象的关系仍有疑惑,本文或许可以解答你的问题。

我们看下面的代码,可以看出和OO语言相比,语法上也有很大分别:

// 定义一个类
class Foo {constructor() {this.a = 'a';}
}//实例化对象
const foo = new Foo();//定义原型的属性
Foo.prototype.b = 'b';//实例可以访问属性
foo.b // "b"//修改原型的属性
Foo.prototype.b= 'B';//实例属性值没有被修改
foo.b // "b"

类已经定义了怎么还能修改呢?prototype又是什么?

不存在面向对象

对于熟悉了面向对象的开发者而言JS中种种非预期操作的存在,都是因为JavaScript中根本没有面向对象的概念,只有对象,没有类。

即使ES6新添了class语法,不意味着JS引入了面向对象,只是原型继承的语法糖。

原型是什么

什么是原型?如果说类是面向对象语言中对象的模版,原型就是 JS中创造对象的模版。

在面向类的语言中,实例化类,就像用模具制作东西一样。实例化一个类就意味着“把类的形态复制到物理对象中”,对于每一个新实例来说都会重复这个过程。

但是在JavaScript中,并没有类似的复制机制。你不能创建一个类的多个实例,只能创建多个对象,它们[[Prototype]]关联的是同一个对象。

//构造函数
function Foo(){
}
//在函数的原型上添加属性
Foo.prototype.prototypeAttribute0 = {status: 'initial'};const foo0 = new Foo();
const foo1 = new Foo();
foo0.prototypeAttribute0 === foo1.prototypeAttribute0 //true

对象、构造函数和原型的关系

当我们创建一个新对象的时候,发生了什么,对象、构造函数和原型到底什么。

先简单地概括:

原型用于定义共享的属性和方法。

构造函数用于定义实例属性和方法,仅负责创造对象,与对象不存在直接的引用关系。

我们先不用class语法糖,这样便于读者理解和暴露出他们之间真正的关系。

// 先创建一个构造函数 定义原型的属性和方法
function Foo() {this.attribute0 = 'attribute0';
}

当创建了一个函数,就会为该函数创建一个prototype属性,它指向函数原型。

所有的原型对象都会自动获得一个constructor属性,这个属性的值是指向原型所在的构造函数的指针。

现在定义原型的属性和方法


Foo.prototype.prototypeMethod0 = function() {console.log('this is prototypeMethod0');
}Foo.prototype.prototypeAttribute0 = 'prototypeAttribute0';

好了,现在,新建一个对象,

const foo = new Foo();foo.attribute0 // "attribute0"
foo.prototypeAttribute0 //"prototypeAttribute0"
foo.prototypeMethod0() // this is prototypeMethod0

它拥有自己的实例属性attribute0,并且可以访问在原型上定义的属性和方法,他们之间的引用关系如图所示。

当调用构造函数创建实例后,该实例的内部会包含一个指针(内部对象),指向构造函数的原型对象。

当读取实例对象的属性时,会在实例中先搜寻,没有找到,就会去原型链中搜索,且总是会选择原型链中最底层的属性进行访问。<!--原型对象自己也可以有原型对象,这样就构成了原型链。关于原型链这里不作过多介绍-->

对象的原型可以通过__proto__在chrome等浏览器上访问。

__proto__是对象的原型指针,prototype是构造函数所对应的原型指针。

语法糖做了什么

ES6推出了class语法,为定义构造函数和原型增加了便利性和可读性。

class Foo {constructor(){this.attribute0 = 'attribute0';}prototypeMethod0(){console.log('this is prototypeMethod0')}
}/* 相当于下面的声明*/
function Foo() {this.attribute0 = 'attribute0';
}Foo.prototype.prototypeMethod0 = function() {console.log('this is prototypeMethod0')
}

class中的constractor相当于构造函数,而class中的方法相当于原型上的方法。、

值得注意的特性

属性屏蔽 —— 避免实例对象无意修改原型

看这段代码,思考输出的结果。

class Foo {prototypeMethod0(){console.log('this is prototypeMethod0')}
}const foo0 = new Foo();
const foo1 = new Foo();foo0.prototypeMethod0 === foo0.__proto__.prototypeMethod0 // truefoo0.prototypeMethod0 = () => console.log('foo0 method');
foo0.prototypeMethod0(); //??
foo1.prototypeMethod0(); //??
foo0.prototypeMethod0 === foo0.__proto__.prototypeMethod0 // ??

输出的结果是

foo0.prototypeMethod0(); // foo0 method
foo1.prototypeMethod0(); // this is prototypeMethod0
foo0.prototypeMethod0 === foo0.__proto__.prototypeMethod0 // false

我们知道对象(即便是原型对象),都是运行时的。

创建之初,foo本身没有prototypeMethod0这个属性,访问foo0.prototypeMethod0将会读取foo0.__proto__.prototypeMethod0

直接修改foo0.prototypeMethod0没有改变__proto__上的方法原因是存在属性屏蔽

现在的情况是:想要修改foo0.prototypeMethod0prototypeMethod0foo中不存在而在上层(即foo.__proto__中存在),并且这不是一个特殊属性(如只读)。

那么会在foo中添加一个新的属性。

这便是为什么直接修改却没有影响__proto__的原因。

<!--更多属性屏蔽的场景也不做赘述-->

小结

再温习一遍这些定义:

原型用于定义共享的属性和方法。

构造函数用于定义实例属性和方法,仅负责创造对象,与对象不存在直接的引用关系。

__proto__是对象的原型指针,prototype是构造函数的原型指针。

在解释原型作用的文章或书籍中,我们会听到继承这样的术语,其实更准确地,委托对于JavaScript中的对象模型来说,是一个更合适的术语。

委托行为意味着某些对象在找不到属性或者方法引用时会把这个请求委托给另一个对象。对象之间的关系不是复制而是委托。


参考

《JavaScript高级程序设计》

《你不知道的JavaScript》

本文仅供解惑,要在脑袋里形成系统的概念,还是要看书呀。

有疑问欢迎大家一起讨论。

写给Java开发者看的JavaScript对象机制相关推荐

  1. 写给Java开发者的Python入门

    编者注:这是一篇来自Java社区的文章,作者的观点是从Java开发者的角度进行讲解的.但是....为了部落的荣耀,人生苦短,我用Python! 从哲学的角度来看,Python和Java是全然相反的.它 ...

  2. 写给Java开发看的 Docker 干货(零基础部署Nginx MySQL SpringBoot)

    前言 Docker是当下使用最多的一种容器技术,想精通Docker并不容易,在公司生产中会有专门的运维人员负责.但是身为开发人员,适当的学习Docker是必要的.Docker的好处这里就不介绍了,网上 ...

  3. 从Java开发者的视角解释JavaScript

    我们无法在一篇博文里解释 JavaScript 的所有细节.如果你正或多或少地涉及了 web 应用程序开发,那么,我们的 Java 工具和技术范围报告揭示了,大多数(71%)Java 开发者被归到了这 ...

  4. json对象,java对象,json字符串,java字符串转换方式,没写的自己去看源码!

    package com.sgai.property;import com.alibaba.druid.support.json.JSONUtils; import com.alibaba.fastjs ...

  5. Java开发者写SQL时常犯的10个错误

    首页 所有文章 资讯 Web 架构 基础技术 书籍 教程 我要投稿 更多频道 » - 导航条 -首页所有文章资讯Web架构基础技术书籍教程我要投稿更多频道 »- iOS- Python- Androi ...

  6. 优秀Java开发者都在看的书籍

    前言介绍: Java作为企业级应用开发的首选,深受互联网大厂的青睐.对于求职找工作的朋友来说,Java可能仍然是后端工程师的优选,虽然Python热度不断上升,但是Java作为老牌语言,在企业中的地位 ...

  7. java 反射 判断是否存在_如何判断Javascript对象是否存在

    Javascript语言的设计不够严谨,很多地方一不小心就会出错. 举例来说,请考虑以下情况. 现在,我们要判断一个全局对象myObj是否存在,如果不存在,就对它进行声明.用自然语言描述的算法如下: ...

  8. JAVA开发者值得一看的几本书,好书推荐

    "学习的最好途径就是看书",这是我自己学习并且小有了一定的积累之后的第一体会.个人认为看书有两点好处: 能出版出来的书一定是经过反复的思考.雕琢和审核的,因此从专业性的角度来说,一 ...

  9. 想入职阿里的Java开发者必看,阿里巴巴面试官实战经验分享!

    最近社区Java技术进阶群的小伙伴总是会问,如何面试阿里Java技术岗,需要什么条件,做哪些准备:小编就这些问题找到了阿里技术团队中在一线真正带Java开发团队并直接参与技术面试的专家,分享了自身在筛 ...

最新文章

  1. 计算机 大学活动 游戏,朝花夕拾”——中国矿业大学计算机学院积极举办“那些年我们一起玩过的游戏”活动...
  2. 奇异值的物理意义是什么?强大的矩阵奇异值分解(SVD)及其应用
  3. 微信公众号的网页授权如何在本地调试?
  4. CodeForces509F Progress Monitoring
  5. 03.Mybatis优化
  6. 数据结构之串:串的模式匹配
  7. java计算器如何实现运算_用java编写了一个模拟计算器的界面设计,怎么实现运算功能呢...
  8. note_maven的基本使用
  9. 【英语学习】【科学】【Glencoe Science】【C】Animal Diversity 目录及术语表
  10. 暴跌300亿,“撤离”中国!乔布斯12年前憋的大招,又把一个巨头逼下神坛
  11. 求助帖--C++中单引号' '内多个字符是什么意思
  12. Caffe学习1 :ProtoBuffer
  13. 单片机c32语言,单片机课件c32IO口.ppt
  14. NPDP产品经理认证:精益画布使用六步法
  15. 苹果手机 计算机 错误,苹果手机的计算器连1+2+3都算不对!原因太尴尬!
  16. Python判断素数
  17. C++入门基础知识总结(2022整理)
  18. Redux Reducer
  19. 枕头的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  20. 一脸懵逼加从入门到绝望学习hadoop之Caused by: java.net.UnknownHostException: master报错

热门文章

  1. sjf调度算法_如何通过静态方法预测SJF调度中未来过程的突发时间?
  2. array_fill_PHP array_fill()函数与示例
  3. scala字符替换_如何替换Scala中的“坏”字符?
  4. c语言 函数的参数传递示例_C语言中带有示例的remove()函数
  5. 三、Beautiful Soup解析库
  6. 分析染色温度对染色热力学性能和染色动力学性能的影响?实际生产时,染色温度选择应考虑哪些因素?举例说明?并阐明实现低温染色的原理。
  7. 栈应用_计算按运算符优先级分布的算式(代码、分析、汇编)
  8. Hibernate中hql的基本查询、条件查询、排序插叙、分页查询、投影查询
  9. phpstud如何安装mysql新版_MySQL_图解MySQL数据库的安装和操作,一、MySQL下载与安装 1、 - phpStudy...
  10. 543. 二叉树的直径 golang