该材料(我们今天发布的翻译版)致力于对象的研究-JavaScript的关键本质之一。 它主要是为想要简化对象知识的初学者开发的。

JavaScript中的对象是属性的动态集合,此外,还包含作为对象原型的“隐藏”属性。 对象的属性由键和值表征。 让我们开始关于带有键的JS对象的讨论。
对象属性键
对象属性键是唯一的字符串。 您可以使用两种方法来访问属性:通过句点访问它们,并在方括号中指定对象键。 通过点访问属性时,键必须是有效的JavaScript标识符。 考虑一个例子:
let obj = { message : "A message" } obj.message //"A message" obj["message"] //"A message"
尝试访问对象的不存在的属性时,不会出现错误消息,但是将返回值
undefined
:
obj.otherProperty
使用方括号访问属性时,可以使用不是有效的JavaScript标识符的键(例如,键可以是包含空格的字符串)。 它们可以具有可以转换为字符串的任何值:
let french = {}; french["merci beaucoup"] = "thank you very much"; french["merci beaucoup"];
如果将非字符串值用作键,则会将它们自动转换为字符串(如果可能,使用
toString()
方法):
et obj = {}; //Number obj[1] = "Number 1"; obj[1] === obj["1"]; //true //Object let number1 = { toString : function() { return "1"; } } obj[number1] === obj["1"]; //true
在此示例中,对象编号1用作键。 尝试访问属性时,该属性将转换为第
1
行,并且此转换的结果将用作键。
对象属性值
对象属性可以是原始值,对象或函数。
▍对象作为对象属性值
可以将对象放置在其他对象中。 考虑
一个例子 :
let book = { title : "The Good Parts", author : { firstName : "Douglas", lastName : "Crockford" } } book.author.firstName;
可以使用类似的方法来创建名称空间:
let app = {}; app.authorService = { getAuthors : function() {} }; app.bookService = { getBooks : function() {} };
▍用作对象属性值
当函数用作对象属性值时,它通常成为对象方法。 在方法内部,要访问当前对象,请使用
this
。
但是,根据调用函数的方式,此关键字可能具有不同的含义。
在这里,您可以阅读有关失去上下文的情况。
物体的动态性质
从本质上讲,JavaScript中的对象是动态实体。 您可以随时向它们添加属性,删除属性也是如此:
let obj = {}; obj.message = "This is a message"; // obj.otherMessage = "A new message"; // delete obj.otherMessage; //
对象作为关联数组
对象可以被视为关联数组。 关联数组键是对象的属性名称。 为了访问键,您无需查看所有属性,也就是说,基于对象访问关联数组键的操作在O(1)时间内执行。
对象原型
对象具有一个“隐藏”链接
__proto__
,指向该对象继承属性的原型对象。
例如,使用对象文字创建的对象具有指向
Object.prototype
的链接:
var obj = {}; obj.__proto__ === Object.prototype;
▍空物体
如我们所见,“空”对象
{}
实际上并不是很空,因为它包含对
Object.prototype
的引用。 为了创建一个真正的空对象,您需要使用以下构造:
Object.create(null)
因此,将创建一个没有原型的对象。 此类对象通常用于创建关联数组。
▍原型链
原型对象可以具有自己的原型。 如果您尝试访问其中不存在的对象的属性,JavaScript将尝试在该对象的原型中找到该属性,如果所需的属性不存在,则将尝试在原型的原型中找到它。 这将一直持续到找到所需的属性或到达原型链的末尾为止。
基本类型值和对象包装器
从语言允许您访问它们的属性和方法的意义上讲,JavaScript使您可以将原始类型的值作为对象来使用。
(1.23).toFixed(1); //"1.2" "text".toUpperCase(); //"TEXT" true.toString(); //"true"
而且,当然,基本类型的值不是对象。
为了组织对基本类型值的“属性”的访问,JavaScript在必要时创建包装器对象,包装器对象在不必要时将被销毁。 JS引擎优化了创建和销毁包装器对象的过程。
对象包装器具有数字,字符串和逻辑类型的值。 对应类型的对象由构造函数
Number
,
String
和
Boolean
。
嵌入式原型
Number对象从原型
Number.prototype
继承属性和方法,该原型是
Number.prototype
的后代:
var no = 1; no.__proto__ === Number.prototype; //true no.__proto__.__proto__ === Object.prototype; //true
字符串对象的原型是
String.prototype
。 布尔对象的原型是
Boolean.prototype
。 数组的原型(也是对象)是
Array.prototype
。
JavaScript中的函数也是具有原型
Function.prototype
对象。 函数具有诸如
bind()
,
apply()
和
call()
。
所有表示原始类型值(
null
和
undefined
值除外)的对象,函数和对象都从
Object.prototype
继承属性和方法。 这导致这样一个事实,例如,它们都具有
toString()
方法。
使用polyfill扩展嵌入式对象
使用JavaScript,可以使用所谓的polyfill轻松扩展具有新功能的嵌入式对象。 polyfill是一段代码,实现了任何浏览器都不支持的功能。
poly使用填充胶
例如,存在
Object.assign()
方法的
Object.assign()
。 如果它不可用,它允许您向
Object
添加新功能。
这同样适用于
Array.from()
,如果
from()
方法不在
Array
对象中,则将其配备此方法。
▍Polyfill和原型
借助polyfill,可以将新方法添加到对象原型中。 例如,
String.prototype.trim()
允许您为所有字符串对象配备
trim()
方法:
let text = " A text "; text.trim(); //"A text"
Array.prototype.find()
允许您为所有数组配备
find()
方法。
Array.prototype.findIndex()
工作方式类似:
let arr = ["A", "B", "C", "D", "E"]; arr.indexOf("C");
单继承
Object.create()
命令允许您使用给定的原型对象创建新对象。 此命令在JavaScript中用于实现单个继承机制。 考虑
一个例子 :
let bookPrototype = { getFullTitle : function(){ return this.title + " by " + this.author; } } let book = Object.create(bookPrototype); book.title = "JavaScript: The Good Parts"; book.author = "Douglas Crockford"; book.getFullTitle();
多重继承
Object.assign()
命令将属性从一个或多个对象复制到目标对象。 它可以用于实现多种继承方案。 这是
一个例子 :
let authorDataService = { getAuthors : function() {} }; let bookDataService = { getBooks : function() {} }; let userDataService = { getUsers : function() {} }; let dataService = Object.assign({}, authorDataService, bookDataService, userDataService ); dataService.getAuthors(); dataService.getBooks(); dataService.getUsers();
不变的对象
使用
Object.freeze()
命令可以“冻结”对象。 您不能向此类对象添加新属性。 属性不能删除,也不能更改其值。 通过使用此命令,对象变为不可变或不可变:
"use strict"; let book = Object.freeze({ title : "Functional-Light JavaScript", author : "Kyle Simpson" }); book.title = "Other title";//: Cannot assign to read only property 'title'
Object.freeze()
命令执行对象的所谓“浅冻结”。 这意味着可以修改嵌套在“冻结”对象中的对象。 为了对对象进行“深度冻结”,您需要递归“冻结”其所有属性。
克隆对象
要创建对象的克隆(副本),可以使用
Object.assign()
命令:
let book = Object.freeze({ title : "JavaScript Allongé", author : "Reginald Braithwaite" }); let clone = Object.assign({}, book);
此命令执行对象的浅表复制,即,它仅复制顶层属性。 嵌套对象对于原始对象及其副本来说是常见的。
对象文字
对象文字为开发人员提供了一种简单直接的方法来创建对象:
let timer = { fn : null, start : function(callback) { this.fn = callback; }, stop : function() {}, }
但是,这种创建对象的方法有缺点。 特别是,使用这种方法,对象的所有属性都是公开可用的,可以重新定义对象的方法,不能将它们用于创建相同对象的新实例:
timer.fn;//null timer.start = function() { console.log("New implementation"); }
Object.create()方法
可以通过联合使用
Object.create()
和
Object.freeze()
方法来解决上述两个问题。
我们将此技术应用于前面的示例。 首先,创建一个冻结的原型
timerPrototype
,其中包含对象的各种实例所需的所有方法。 之后,创建一个对象,它是
timerPrototype
的后继对象:
let timerPrototype = Object.freeze({ start : function() {}, stop : function() {} }); let timer = Object.create(timerPrototype); timer.__proto__ === timerPrototype; //true
如果保护原型不受更改,则作为其继承人的对象将无法更改原型中定义的属性。 现在,
start()
和
stop()
方法不能被覆盖:
"use strict"; timer.start = function() { console.log("New implementation"); }
Object.create(timerPrototype)
可用于创建具有相同原型的多个对象。
构造函数
JavaScript具有所谓的构造函数,这些函数是执行上述步骤创建新对象的“语法糖”。 考虑
一个例子 :
function Timer(callback){ this.fn = callback; } Timer.prototype = { start : function() {}, stop : function() {} } function getTodos() {} let timer = new Timer(getTodos);
您可以使用任何函数作为构造函数。 使用
new
关键字调用构造函数。 使用名为
FunctionConstructor
的构造函数创建的对象将收到
FunctionConstructor.prototype
原型:
let timer = new Timer(); timer.__proto__ === Timer.prototype;
在这里,再次防止冻结原型,您可以冻结原型:
Timer.prototype = Object.freeze({ start : function() {}, stop : function() {} });
new关键字新
当执行形式为
new Timer()
的命令时,将执行与
newTimer()
函数以下相同的操作:
function newTimer(){ let newObj = Object.create(Timer.prototype); let returnObj = Timer.call(newObj, arguments); if(returnObj) return returnObj; return newObj; }
在这里创建一个新对象,其原型是
Timer.prototype
。 然后调用
Timer
函数,为新对象设置字段。
类关键字
ECMAScript 2015引入了一种执行上述操作的新方法,这是另一批“语法糖”。 我们正在谈论
class
关键字及其相关的构造。 考虑
一个例子 :
class Timer{ constructor(callback){ this.fn = callback; } start() {} stop() {} } Object.freeze(Timer.prototype);
使用基于名为
ClassName
的类的
class
关键字创建的对象将具有原型
ClassName.prototype
。 在基于类创建对象时,请使用
new
关键字:
let timer= new Timer(); timer.__proto__ === Timer.prototype;
使用类不会使原型不变。 如有必要,必须像我们已经做过的那样“冻结”它们:
Object.freeze(Timer.prototype);
基于原型的继承
在JavaScript中,对象从其他对象继承属性和方法。 构造函数和类是“语法糖”,用于创建包含所有必要方法的原型对象。 使用它们,将创建作为原型继承人的新对象,使用构造函数或类机制来设置特定于特定实例的属性。
如果构造函数和类可以自动使原型不变,那就太好了。
原型继承的优势是节省内存。 事实是,原型仅创建一次,然后在其基础上创建的所有对象都将使用它。
the缺乏内置封装机制的问题
原型继承模板不使用将对象的属性分为私有和公共属性。 对象的所有属性都是公开可用的。
例如,
Object.keys()
命令返回一个包含对象的所有属性键的数组。 它可用于遍历对象的所有属性:
function logProperty(name){ console.log(name); // console.log(obj[name]); // } Object.keys(obj).forEach(logProperty);
有一种模式可以模仿私有属性,具体取决于开发人员将不会访问名称以下划线(
_
)开头的那些属性:
class Timer{ constructor(callback){ this._fn = callback; this._timerId = 0; } }
工厂特色
可以使用工厂函数来创建JavaScript中的封装对象。 看起来像这样:
function TodoStore(callback){ let fn = callback; function start() {}, function stop() {} return Object.freeze({ start, stop }); }
在这里,变量
fn
是私有的。 只有
start()
和
stop()
方法是公开可用的。 这些方法不能在外部进行修改。 这里不使用this关键字,因此,当使用这种创建对象的方法时,丢失
this
上下文的问题是不相关的。
return
命令使用仅包含函数的对象文字。 此外,这些函数在闭包中声明;它们具有相同的状态。 要冻结对象的公共API,请使用已知的
Object.freeze()
命令。
在这里,在示例中,我们使用了
Timer
对象。 在
此材料中,您可以找到其完整实现。
总结
在JavaScript中,原始类型,普通对象和函数的值被视为对象。 对象具有动态性质,可以用作关联数组。 对象是其他对象的继承者。 构造函数和类是“语法糖”;它们使您可以基于原型创建对象。 您可以使用
Object.create()
方法组织单个继承,并使用
Object.create()
组织多个继承。 您可以使用工厂函数来创建封装的对象。
亲爱的读者们! 如果您是从其他语言来学习JavaScript的,请与我们以已知语言实现的对象进行比较,告诉我们您对JS对象的喜欢或不喜欢的地方。
