- 原文地址: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