写给Java开发者看的JavaScript对象机制

智极漫步
• 阅读 2757

帮助面向对象开发者理解关于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属性,这个属性的值是指向原型所在的构造函数的指针。

写给Java开发者看的JavaScript对象机制

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


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,并且可以访问在原型上定义的属性和方法,他们之间的引用关系如图所示。

写给Java开发者看的JavaScript对象机制

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

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

对象的原型可以通过__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 // true

foo0.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》

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

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

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Karen110 Karen110
3年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
3年前
JavaScript的 基本数据类型
第一:Javascript对象是第二:Javascript中第三:Javascript的对象是数据;第四:JavaScript中的对象可以简单理解成"名称:值"对(name:value)。名称(name):"名称"部分是一个JavaScript字符串参考https://www
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
JS原型、原型链深入理解
原型是JavaScript中一个比较难理解的概念,原型相关的属性也比较多,对象有”prototype”属性,函数对象有”prototype”属性,原型对象有”constructor”属性。原型是JavaScript中一个比较难理解的概念,原型相关的属性也比较多,对象有”prototype”属性,函数对象有”prototype”属性,原型对
Wesley13 Wesley13
3年前
JS篇(004)
答案:1.脚本语言。JavaScript是一种解释型的脚本语言,C、C等语言先编译后执行,而JavaScript是在程序的运行过程中逐行进行解释。2.基于对象。JavaScript是一种基于对象的脚本语言,它不仅可以创建对象,也能使用现有的对象。3.简单。JavaScript语言中采用的是弱类型的变量
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
一点一木 一点一木
8个月前
JavaScript中的面向对象编程(OOP) - 终极指南
本文介绍了JavaScript的面向对象编程(OOP)概念,包括继承、多态、封装和抽象等关键要素,并通过代码示例帮助开发者理解和应用OOP思维。
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
智极漫步
智极漫步
Lv1
阶下青苔与红树,雨中寥落月中愁。
文章
3
粉丝
0
获赞
0