JavaScript:探索对象

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



JavaScript中的对象是属性的动态集合,此外,还包含作为对象原型的“隐藏”属性。 对象的属性由键和值表征。 让我们开始关于带有键的JS对象的讨论。

对象属性键


对象属性键是唯一的字符串。 您可以使用两种方法来访问属性:通过句点访问它们,并在方括号中指定对象键。 通过点访问属性时,键必须是有效的JavaScript标识符。 考虑一个例子:

let obj = {  message : "A message" } obj.message //"A message" obj["message"] //"A message" 

尝试访问对象的不存在的属性时,不会出现错误消息,但是将返回值undefined

 obj.otherProperty //undefined 

使用方括号访问属性时,可以使用不是有效的JavaScript标识符的键(例如,键可以是包含空格的字符串)。 它们可以具有可以转换为字符串的任何值:

 let french = {}; french["merci beaucoup"] = "thank you very much"; french["merci beaucoup"]; //"thank you very much" 

如果将非字符串值用作键,则会将它们自动转换为字符串(如果可能,使用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; //"Douglas" 

可以使用类似的方法来创建名称空间:

 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; //true 

▍空物体


如我们所见,“空”对象{}实际上并不是很空,因为它包含对Object.prototype的引用。 为了创建一个真正的空对象,您需要使用以下构造:

 Object.create(null) 

因此,将创建一个没有原型的对象。 此类对象通常用于创建关联数组。

▍原型链


原型对象可以具有自己的原型。 如果您尝试访问其中不存在的对象的属性,JavaScript将尝试在该对象的原型中找到该属性,如果所需的属性不存在,则将尝试在原型的原型中找到它。 这将一直持续到找到所需的属性或到达原型链的末尾为止。

基本类型值和对象包装器


从语言允许您访问它们的属性和方法的意义上讲,JavaScript使您可以将原始类型的值作为对象来使用。

 (1.23).toFixed(1); //"1.2" "text".toUpperCase(); //"TEXT" true.toString(); //"true" 

而且,当然,基本类型的值不是对象。

为了组织对基本类型值的“属性”的访问,JavaScript在必要时创建包装器对象,包装器对象在不必要时将被销毁。 JS引擎优化了创建和销毁包装器对象的过程。

对象包装器具有数字,字符串和逻辑类型的值。 对应类型的对象由构造函数NumberStringBoolean

嵌入式原型


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()

所有表示原始类型值( nullundefined值除外)的对象,函数和对象都从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"); //2 

单继承


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();//JavaScript: The Good Parts by Douglas Crockford 

多重继承


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"); } //: Cannot assign to read only property 'start' of object 

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对象的喜欢或不喜欢的地方。

Source: https://habr.com/ru/post/zh-CN420615/


All Articles