-
Notifications
You must be signed in to change notification settings - Fork 681
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
【JS 口袋书】第 5 章:JS 对象生命周期的秘密 #126
Comments
如何创建不可变的 JS 对象?
“prototype” 是什么? 什么是构造函数调用? 什么是构造函数? 可以描述一下 new 在底层下做了哪些事吗?
|
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
为了保证的可读性,本文采用意译而非直译。
一切皆对象
咱们经常听到JS中“一切皆对象”? 有没有问想过这是什么意思? 其它语言也有“一切皆对象”之说,如
Python
。 但是Python
中的对象不仅仅是像JS对象这样的存放值和值的容器。Python
中的对象是一个类。 JS中有类似的东西,但JS中的“对象”只是键和值的容器:实际上,JS中的对象是一种“哑”类型,但很多其他实体似乎都是从对象派生出来的。 甚至是数组,在JS中创建一个数组,如下所示:
然后用
typeof
运算符检查类型,会看到一个令人惊讶的结果:看来数组是一种特殊的对象! 即使JS中的函数也是对象。 如果你深入挖掘,还有更多,创建一个函数,该函数就会附加一些方法:
输出:
咱们并没有在函数声明
toString
方法,所以在底层一定还有东西。它从何而来?Object
有一个名为.toString
的方法。 似乎咱们的函数具有相同的Object
方法。这时咱们使用浏览器控制台来查看默认被附加的函数和属性,这个谜团就会变得更加复杂:
谁把这些方法放在函数呢。 JS中的函数是一种特殊的对象,这会不会是个暗示? 再看看上面的图片:我们的函数中有一个名为
prototype
的奇怪命名属性,这又是什么鬼?JS中的
prototype
是一个对象。 它就像一个背包,附着在大多数JS内置对象上。 例如Object
,Function
,Array
,Date
,Error
,都有一个“prototype
”:注意内置对象有大写字母:
以下除了
Object
是类型之外,其它是JS的基本类型。另一方面,内置对象就像JS类型的镜像,也用作函数。例如,可以使用String
作为函数将数字转换为字符串:现在回到“
prototype
”。prototype
是所有公共方法和属性的宿主,从祖先派生的“子”对象可以从使用祖先的方法和属性。也就是说,给定一个原始prototype
,咱们可以创建新的对象,这些对象将使用一个原型作为公共函数的真实源,不 Look see see。假设有个要求创建一个聊天应用程序,有个人物对象。这个人物可以发送消息,登录时,会收到一个问候。
根据需求咱们很容易定义这个么一
Person
对象:你可能会想知道,为什么这里要使用字面量的方式来声明
Person
对象。 稍后会详细说明,现在该Person
为“模型”
。通过这个模型,咱们使用Object.create()
来创建以为这个模型为基础的对象。创建和链接对象
JS中对象似乎以某种方式链接在一起,
Object.create()
说明了这一点,此方法从原始对象开始创建新对象,再来创建一个新Person
对象:现在,Tom 是一个新的对象,但是咱们没有指定任何新的方法或属性,但它仍然可以访问
Person
中的name
和age
属性。现在,可以从一个共同的祖先开始创建新的person。但奇怪的是,新对象仍然与原始对象保持连接,这不是一个大问题,因为“子”对象可以自定义属性和方法
这种方式被称为“屏蔽”原始属性。 还有另一种将属性传递给新对象的方法。
Object.create
将另一个对象作为第二个参数,可以在其中为新对象指定键和值:以这种方式配置的属性默认情况下不可写,不可枚举,不可配置。 不可写意味着之后无法更改该属性,更改会被忽略:
不可枚举意味着属性不会在
for...in
循环中显示,例如:但是正如咱们所看到的,由于JS引擎沿着原型链向上查找,在“父”对象上找到
greet
属性。最后,不可配置意味着属性既不能修改也不能删除。如果要更改属性的行为,只需配
writable
(可写性),configurable
(可配置),enumerable
(可枚举)属性即可。现在,
Tom
也可以通过以下方式访问greet()
:暂时不要过于担心“
this
”。 拉下来会详细介绍。暂且先记住,“this”是对函数执行的某个对象的引用。在咱们的例子中,greet()
在Tom
的上下文中运行,因此可以访问“this.name
”。构建JavaScript对象
目前为止,只介绍了关于“prototype”的一点知识 ,还有玩了一会
Object.create()
之外但咱们没有直接使用它。 随着时间的推移出现了一个新的模式:构造函数
。 使用函数创建新对象听起来很合理, 假设你想将Person
对象转换为函数,你可以用以下方式:因此,不需要到处调用
object.create()
,只需将Person
作为函数调用:构造函数模式有助于封装一系列JS对象的创建和配置。 在这里, 咱们使用字面量的方式创建对象。 这是一种从面向对象语言借用的约定,其中类名开头要大写。
上面的例子有一个严重的问题:每次咱们创建一个新对象时,一遍又一遍地重复创建
greet()
函数。可以使用Object.create()
,它会在对象之间创建链接,创建次数只有一次。 首先,咱们将greet()
方法移到外面的一个对象上。 然后,可以使用Object.create()
将新对象链接到该公共对象:这种方式比刚开始会点,还可以进一步优化就是使用
prototype
,prototype
是一个对象,可以在上面扩展属性,方法等等。移除了
personMethods
。 调整Object.create
的参数,否则新对象不会自动链接到共同的祖先:现在公共方法的来源是
Person.prototype
。 使用JS中的new
运算符,可以消除Person
中的所有噪声,并且只需要为this
分配参数。下面代码:
改成:
完整代码:
注意,使用
new
关键字,被称为“构造函数调用”
,new
干了三件事情创建一个空对象
将空对象的
__proto__
指向构造函数的prototype
使用空对象作为上下文的调用构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
根据上面描述的,
new Person("Valentino")
做了:var obj = {}
__proto__
指向构造函数的 prototype:obj.__proto__ = Person().prototype
Person.call(obj)
检查原型链
检查JS对象之间的原型链接有很多种方法。 例如,
Object.getPrototypeOf
是一个返回任何给定对象原型的方法。 考虑以下代码:检查
Person
是否是Tom
的原型:当然,如果使用构造函数调用构造对象,
Object.getPrototypeOf
也可以工作。 但是应该检查原型对象,而不是构造函数本身:除了
Object.getPrototypeOf
之外,还有另一个方法isPrototypeOf
。 该方法用于测试一个对象是否存在于另一个对象的原型链上,如下所示,检查me
是否在Person.prototype
上:instanceof
运算符也可以用于测试构造函数的prototype
属性是否出现在对象的原型链中的任何位置。 老实说,这个名字有点误导,因为JS中没有“实例”。 在真正的面向对象语言中,实例是从类创建的新对象。 请考虑Python中的示例。 咱们有一个名为Person
的类,咱们从该类创建一个名为“tom”的新实例:注意,在Python中没有
new
关键字。现在,咱们可以使用isinstance
方法检查tom
是否是Person
的实例Tom
也是Python
中“object
”的一个实例,下面的代码也返回true
:根据
isinstance
文档,“如果对象参数是类参数的实例,或者是它的(直接、间接或虚拟)子类的实例,则返回true
”。咱们在这里讨论的是类。现在让咱们看看instanceof
做了什么。咱们将从JS中的Person
函数开始创建tom
(因为没有真正的类)使用
isinstance
方法检查tom
是否是Person
和Object
的实例因此,可以得出结论:JS对象的原型总是连接到直接的“父对象”和
Object.prototype
。没有像Python
或Java
这样的类。JS是由对象组成,那么什么是原型链呢?如果你注意的话,咱们提到过几次“原型链”。JS对象可以访问代码中其他地方定义的方法,这看起来很神奇。再次考虑下面的例子:即使该方法不直接存在于“
Tom
”对象上,Tom
也可以访问greet()
。这是JS的一个内在特征,它从另一种称为
Self
的语言中借用了原型系统。 当访问greet()
时,JS引擎会检查该方法是否可直接在Tom
上使用。 如果不是,搜索将继续向上链接,直到找到该方法。“链”是
Tom
连接的原型对象的层次结构。 在我们的例子中,Tom
是Person
类型的对象,因此Tom
的原型连接到Person.prototype
。 而Person.prototype
是Object
类型的对象,因此共享相同的Object.prototype
原型。 如果在Person.prototype
上没有greet()
,则搜索将继续向上链接,直到到达Object.prototype
。 这就是咱们所说的**“原型链”**。保护对象不受操纵
大多数情况下,JS 对象“可扩展”是必要的,这样咱们可以向对象添加新属性。 但有些情况下,我们希望对象不受进一步操纵。 考虑一个简单的对象:
默认情况下,每个人都可以向该对象添加新属性
Object.preventExtensions()
方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。这种技术对于“保护”代码中的关键对象非常方便。JS 中还有许多预先创建的对象,它们都是为扩展而关闭的,从而阻止开发人员在这些对象上添加新属性。这就是“重要”对象的情况,比如
XMLHttpRequest
的响应。浏览器供应商禁止在响应对象上添加新属性这是通过在“response”对象上内部调用
Object.preventExtensions
来完成的。 您还可以使用Object.isExtensible
方法检查对象是否受到保护。 如果对象是可扩展的,它将返回true
:如果对象不可扩展的,它将返回
false
:当然,对象的现有属性可以更改甚至删除
现在,为了防止这种操作,可以将每个属性定义为不可写和不可配置。为此,有一个方法叫
Object.defineProperties
。或者,更方便的是,可以在原始对象上使用
Object.freeze
:Object.freeze
工作方式与Object.preventExtensions
相同,并且它使所有对象的属性不可写且不可配置。 唯一的缺点是“Object.freeze
”仅适用于对象的第一级:嵌套对象不受操作的影响。class
有大量关于ES6 类的文章,所以在这里只讨论几点。JS是一种真正的面向对象语言吗?看起来是这样的,如果咱们看看这段代码
语法与
Python
等其他编程语言中的类非常相似:或 PHP
ES6中引入了类。但是在这一点上,咱们应该清楚JS中没有“
真正的
”类。 一切都只是一个对象,尽管有关键字class
,“原型系统”仍然存在。 新的JS版本是向后兼容的,这意味着在现有功能的基础上添加了新功能,这些新功能中的大多数都是遗留代码的语法糖。总结
JS中的几乎所有东西都是一个对象。 从字面上看。 JS对象是键和值的容器,也可能包含函数。
Object
是JS中的基本构建块:因此可以从共同的祖先开始创建其他自定义对象。 然后咱们可以通过语言的内在特征将对象链接在一起:原型系统。从公共对象开始,可以创建共享原始“父”的相同属性和方法的其他对象。 但是它的工作方式不是通过将方法和属性复制到每个孩子,就像OOP语言那样。 在JS中,每个派生对象都保持与父对象的连接。 使用
Object.create
或使用所谓的构造函数创建新的自定义对象。 与new
关键字配对,构造函数类似于模仿传统的OOP类。思考
new
在底层下做了哪些事吗?代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
原文:https://github.com/valentinogagliardi/Little-JavaScript-Book/blob/v1.0.0/manuscript/chapter5.md
交流
阿里云最近在做活动,低至2折,有兴趣可以看看:https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=pxuujn3r
干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。
因为篇幅的限制,今天的分享只到这里。如果大家想了解更多的内容的话,可以去扫一扫每篇文章最下面的二维码,然后关注咱们的微信公众号,了解更多的资讯和有价值的内容。
每次整理文章,一般都到2点才睡觉,一周4次左右,挺苦的,还望支持,给点鼓励
The text was updated successfully, but these errors were encountered: