- 原文地址:Let’s explore objects in JavaScript
- 原文做者:Cristi Salcescu
- 译文出自:阿里云翻译小组
- 译文连接:github.com/dawn-teams/…
- 译者:灵沼
- 校对者:也树,眠云
对象是多个属性的动态集合,它有一个连接着原型的隐藏属性(注:__proto__
)。javascript
一个属性拥有一个 key 和一个 value 。java
属性的 key 是一个惟一的字符串。git
访问属性有两种方式:点表示法和括号表示法。当使用点表示法,属性的 key 必须是有效的标识符。github
let obj = {
message : "A message"
}
obj.message //"A message"
obj["message"] //"A message"
复制代码
访问一个不存在的属性不会抛出错误,可是会返回 undefined
。数组
obj.otherProperty //undefined
复制代码
当使用括号表示法,属性的 key 不要求是有效的标识符 —— 能够是任意值。浏览器
let french = {};
french["thank you very much"] = "merci beaucoup";
french["thank you very much"]; //"merci beaucoup"
复制代码
当属性的key是一个非字符串的值,会用toString()方法(若是可用的话)把它转换为字符串。bash
let obj = {};
//Number
obj[1] = "Number 1";
obj[1] === obj["1"]; //true
//Object
let number1 = {
toString : function() { return "1"; }
}
obj[number1] === obj["1"]; //true
复制代码
在上面的示例中,对象 number1
被用做一个 key 。它会被转换为字符串,转换结果 “1” 被用做属性的 key 。闭包
属性的值能够是任意的基础数据类型,对象,或函数。app
对象能够嵌套在其余对象里。看下面这个例子:函数
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
关键字表明着当前的对象。
this
,会根据函数的调用方式有不一样的值。了解更多关于this
丢失上下文的问题,能够查看当"this"丢失上下文时应该怎么办。
对象本质上就是动态的。能够任意添加删除属性。
let obj = {};
obj.message = "This is a message"; //add new property
obj.otherMessage = "A new message"; //add new property
delete obj.otherMessage; //delete property
复制代码
咱们能够把对象当作一个 Map。Map 的 key 就是对象的属性。
访问一个 key 不须要去扫描全部属性。访问的时间复杂度是 o(1)。
对象有一个连接着原型对象的“隐藏”属性 __proto__
,对象是从这个原型对象中继承属性的。
举个例子,使用对象字面量建立的对象有一个指向 Object.prototype
的连接:
var obj = {};
obj.__proto__ === Object.prototype; //true
复制代码
原型对象有它本身的原型。当一个属性被访问的时候而且不包含在当前对象中,JavaScript会沿着原型链向下查找直到找到被访问的属性,或者到达 null
为止。
原型只用于读取值。对象进行更改时,只会做用到当前对象,不会影响对象的原型;就算原型上有同名的属性,也是如此。
正如咱们看到的,空对象 {}
并非真正意义上的空,由于它包含着指向 Object.prototype
的连接。为了建立一个真正的空对象,咱们可使用 Object.create(null)
。它会建立一个没有任何属性的对象。这一般用来建立一个Map。
在容许访问属性这一点上,JavaScript 把原始值描述为对象。固然了,原始值并非对象。
(1.23).toFixed(1); //"1.2"
"text".toUpperCase(); //"TEXT"
true.toString(); //"true"
复制代码
为了容许访问原始值的属性, JavaScript 创造了一个包装对象,而后销毁它。JavaScript引擎对建立包装和销毁包装对象的过程作了优化。
数值、字符串和布尔值都有等效的包装对象。跟别是:Number
、String
、Boolean
。
null
和 undefined
原始值没有相应的包装对象而且不提供任何方法。
Numbers 继承自Number.prototype
,Number.prototype
继承自Object.prototype
。
var no = 1;
no.__proto__ === Number.prototype; //true
no.__proto__.__proto__ === Object.prototype; //true
复制代码
Strings 继承自 String.prototype
。Booleans 继承自 Boolean.prototype
函数都是对象,继承自 Function.prototype
。函数拥有 bind()
、apply()
和 call()
等方法。
全部对象、函数和原始值(除了 null
和 undefined
)都从 Object.prototype
继承属性。他们都有 toString()
方法。
JavaScript 能够轻松地使用新功能扩充内置对象。
polyfill 就是一个代码片断,用于在不支持某功能的浏览器中实现该功能。
举个例子,这个为 Object.assign()
写的polyfill,若是它不可用,那么就在 Object
上添加一个新方法。
为 Array.from()
写了相似的polyfill,若是它不可用,就在 Array
上添加一个新方法。
新的方法能够被添加到原型。
举个例子,String.prototype.trim()
polyfill让全部的字符串都能使用 trim()
方法。
let text = " A text ";
text.trim(); //"A text"
复制代码
Array.prototype.find()
polyfill让全部的数组都能使用find()
方法。polyfill也是一样的。
let arr = ["A", "B", "C", "D", "E"];
arr.indexOf("C"); //2
复制代码
Object.create()
用特定的原型对象建立一个新对象。它用来作单一继承。思考下面的例子:
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);
复制代码
Object.assign()
执行浅拷贝,不是深拷贝。它拷贝对象的第一层属性。嵌套的对象会在原始对象和副本对象之间共享。
对象字面量提供一种简单、优雅的方式建立对象。
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.freeze()
一块儿可以解决最后两个问题。
首先,我要使用全部方法建立一个冻结原型 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);
复制代码
全部的以 function
关键字定义的函数均可以做为构造函数。构造函数使用功能 new
调用。新对象将原型设定为 FunctionConstructor.prototype
。
let timer = new Timer();
timer.__proto__ === Timer.prototype;
复制代码
一样地,咱们须要冻结原型来防止方法被重定义。
Timer.prototype = Object.freeze({
start : function() {},
stop : function() {}
});
复制代码
当执行 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
函数并为新对象设置属性字段。
ES2015为这一切带来了更好的语法糖。看下面的例子:
class Timer{
constructor(callback){
this.fn = callback;
}
start() {}
stop() {}
}
Object.freeze(Timer.prototype);
复制代码
使用 class 构建的对象将原型设置为 ClassName.prototype
。在使用类建立对象时,必须使用 new
操做符。
let timer= new Timer();
timer.__proto__ === Timer.prototype;
复制代码
class 语法不会冻结原型,因此咱们须要在以后进行操做。
Object.freeze(Timer.prototype);
复制代码
在 JavaScript 中,对象继承自对象。
构造函数和类都是用来建立原型对象的全部方法的语法糖。而后它建立一个继承自原型对象的新对象,并为新对象设置数据字段 基于原型的继承具备保护记忆的好处。原型只建立一次而且由全部的实例使用。
基于原型的继承模式没有私有性。全部对象的属性都是公有的。
Object.keys()
返回一个包含全部属性键的数组。它能够用来迭代对象的全部属性。
function logProperty(name){
console.log(name); //property name
console.log(obj[name]); //property value
}
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()
方法是公有的。start()
和 stop()
方法不能被外界改变。这里没有使用 this
,因此没有 this
丢失上下文的问题。
对象字面量依然用于返回对象,可是此次它只包含函数。更重要的是,这些函数是共享相同私有状态的闭包。 Object.freeze()
被用来冻结公有 API。
Timer 对象的完整实现,请看具备封装功能的实用JavaScript对象.
JavaScript 像对象同样处理原始值、对象和函数。
对象本质上是动态的,能够用做 Map。
对象继承自其余对象。构造函数和类是建立从其余原型对象继承的对象的语法糖。
Object.create()
能够用来单一继承,Object.assign()
用来多重继承。
工厂函数能够构建封装对象。
有关 JavaScript 功能的更多信息,请看:
Discover the power of first class functions
How point-free composition will make you a better functional programmer
Here are a few function decorators you can write from scratch