ECMAScript 6教程 (二) 物件和函式

langyahappy發表於2015-07-24

物件

屬性的簡潔表示法

ES6允許直接寫入變數和函式,作為物件的屬性和方法。這樣的書寫更加簡潔。

function f( x, y ) {
    return { x, y };
}
// 等同於
function f( x, y ) {
    return { x: x, y: y };
}

示例:

var Person = {
name: '張三',
birth:'1990-01-01',
// 等同於hello: function ()...
hello() { document.write('我的名字是', this.name); }
};
Person.hello();

這種寫法用於函式的返回值,將會非常方便。

function getPoint() {
  var x = 1;
  var y = 10;
  return {x, y};
}
getPoint() // {x:1, y:10}

屬性名錶達式

JavaScript語言定義物件的屬性,有兩種方法。

let obj = {};
// 方法一
obj.foo = true;
// 方法二
obj['a'+'bc'] = 123;
document.write(obj);

上面程式碼的方法一是直接用識別符號作為屬性名,方法二是用表示式作為屬性名,這時要將表示式放在方括號之內。

如果使用字面量方式定義物件(使用大括號),在ES5中只能使用方法一(識別符號)定義屬性。

var obj = {
  foo: true,
  abc: 123
};

ES6允許字面量定義物件時,用方法二(表示式)作為物件的屬性名,即把表示式放在方括號內。

let propKey = 'foo';

let obj = {
[propKey]: true,
['a'+'bc']: 123
};

表示式還可以用於定義方法名。

let obj = {
  ['h'+'ello']() {
    return 'hi';
  }
};

document.write(obj.hello()); // hi

比較兩個值是否嚴格相等

Object.is()用來比較兩個值是否嚴格相等。它與嚴格比較運算子(===)的行為基本一致,不同之處只有兩個:一是+0不等於-0,二是NaN等於自身。

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

源物件的所有可列舉屬性,複製到目標物件

Object.assign方法用來將源物件(source)的所有可列舉屬性,複製到目標物件(target)。它至少需要兩個物件作為引數,第一個引數是目標物件,後面的引數都是源物件。只要有一個引數不是物件,就會丟擲TypeError錯誤。

var target = { a: 1 };

var source1 = { b: 2 };
var source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

注意,如果目標物件與源物件有同名屬性,或多個源物件有同名屬性,則後面的屬性會覆蓋前面的屬性。

var target = { a: 1, b: 1 };

var source1 = { b: 2, c: 2 };
var source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

proto屬性

proto屬性,用來讀取或設定當前物件的prototype物件。該屬性一度被正式寫入ES6草案,但後來又被移除。目前,所有瀏覽器(包括IE11)都部署了這個屬性。

// es6的寫法

var obj = {
__proto__: someOtherObj,
method: function() { ... }
}



// es5的寫法

var obj = Object.create(someOtherObj);
obj.method = function() { ... }

Symbol型別

ES6引入了一種新的原始資料型別Symbol,表示獨一無二的ID。凡是屬性名屬於Symbol型別,就都是獨一無二的,可以保證不會與其他屬性名產生衝突。

let s = Symbol();

typeof s
// "symbol"

typeof運算子的結果,表明變數s是Symbol資料型別,而不是字串之類的其他型別。

注意,Symbol函式前不能使用new命令,否則會報錯。這是因為生成的Symbol是一個原始型別的值,不是物件。

Symbol型別的值不能與其他型別的值進行運算,會報錯。

var sym = Symbol('My symbol');

"your symbol is " + sym
// TypeError: can't convert symbol to string
`your symbol is ${sym}`
// TypeError: can't convert symbol to string

但是,Symbol型別的值可以轉為字串。

var sym = Symbol('My symbol');

String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'

內建代理

Proxy 內建的一個代理工具,使用他可以在物件處理上加一層屏障:

S6原生提供Proxy建構函式,用來生成Proxy例項。

    var proxy = new Proxy(target, handler)
    new Proxy()表示生成一個Proxy例項,它的target參數列示所要攔截的目標物件,handler引數也是一個物件,用來定製攔截行為。

    var plain = {
    name : "hubwiz"
    };
    var proxy = new Proxy(plain, {
    get: function(target, property) {
    return property in target ? target[property] : "匯智網";
    }
    });


proxy.name // "hubwiz"
proxy.title // "匯智網"

Proxy(target, handler), 這裡的 handler有如下的方法:

  • get(target, propKey,receiver):攔截物件屬性的讀取,比如proxy.foo和proxy['foo'],返回型別不限。最後一個引數receiver可選,當target物件設定了propKey屬性的get函式時,receiver物件會繫結get函式的this物件。
  • set(target, propKey, value, receiver):攔截物件屬性的設定,比如proxy.foo = v或proxy['foo'] = v,返回一個布林值。
  • has(target, propKey):攔截propKey in proxy的操作,返回一個布林值。
  • deleteProperty(target, propKey) :攔截delete proxy[propKey]的操作,返回一個布林值。
  • enumerate(target):攔截for (var x in proxy),返回一個遍歷器。
  • hasOwn(target, propKey):攔截proxy.hasOwnProperty('foo'),返回一個布林值。
  • ownKeys(target):攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一個陣列。該方法返回物件所有自身的屬性,而Object.keys()僅返回物件可遍歷的屬性。
  • getOwnPropertyDescriptor(target, propKey) :攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述物件。
  • defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個布林值。
  • preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個布林值。
  • getPrototypeOf(target) :攔截Object.getPrototypeOf(proxy),返回一個物件。
  • isExtensible(target):攔截Object.isExtensible(proxy),返回一個布林值。
  • setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個布林值。

如果目標物件是函式,那麼還有兩種額外操作可以攔截。

  • apply(target, object, args):攔截Proxy例項作為函式呼叫的操作,比如proxy(...args)、roxy.call(object, ...args)、proxy.apply(...)。
  • construct(target, args, proxy):攔截Proxy例項作為建構函式呼叫的操作,比如new proxy(...args)。

函式

預設引數

現在可以在定義函式的時候指定引數的預設值了,而不用像以前那樣通過邏輯或操作符來達到目的了。

function sayHello(name){
//傳統的指定預設引數的方式
var name = name||'hubwiz';
document.write('Hello '+name);
}

//運用ES6的預設引數
function sayHello2(name='hubwiz'){
document.write(`Hello ${name}`);
}
sayHello(); //輸出:Hello hubwiz
sayHello('匯智網'); //輸出:Hello 匯智網
sayHello2(); //輸出:Hello hubwiz
sayHello2('匯智網'); //輸出:Hello 匯智網

rest引數

rest引數(形式為“...變數名”)可以稱為不定引數,用於獲取函式的多餘引數,這樣就不需要使用arguments物件了。

rest引數搭配的變數是一個陣列,該變數將多餘的引數放入陣列中。

function add(...values) {
  let sum = 0;

  for (var val of values) {
    sum += val;
  }
  return sum;
}

add(1, 2, 3) // 6

不定引數的格式是三個句點後跟代表所有不定引數的變數名。比如以上示例中,...values 代表了所有傳入add函式的引數。

擴充套件運算子

擴充套件運算子(spread)是三個點(...)。它好比rest引數的逆運算,將一個陣列轉為用逗號分隔的引數序列。該運算子主要用於函式呼叫。

它允許傳遞陣列或者類陣列直接做為函式的引數而不用通過apply。

var people=['張三','李四','王五'];

//sayHello函式本來接收三個單獨的引數people1,people2和people3
function sayHello(people1,people2,people3){
document.write(`Hello ${people1},${people2},${people3}`);
}

//但是我們將一個陣列以擴充引數的形式傳遞,它能很好地對映到每個單獨的引數
sayHello(...people); //輸出:Hello 張三,李四,王五

//而在以前,如果需要傳遞陣列當引數,我們需要使用函式的apply方法
sayHello.apply(null,people); //輸出:Hello 張三,李四,王五

箭頭函式

箭頭函式是使用=>語法的函式簡寫形式。這在語法上與 C#、Java 8 和 CoffeeScript 的相關特性非常相似。

var array = [1, 2, 3];
//傳統寫法
array.forEach(function(v, i, a) {
  document.write(v);
});
//ES6
array.forEach(v => document.write(v));

它們同時支援表示式體和語句體。與(普通的)函式所不同的是,箭頭函式和其上下文中的程式碼共享同一個具有詞法作用域的this。

var evens = [1,2,3,4,5];
var fives = [];
// 表示式體
var odds = evens.map(v => v + 1);
var nums = evens.map((v, i) => v + i);
var pairs = evens.map(v => ({even: v, odd: v + 1}));

// 語句體
nums.forEach(v => {
  if (v % 5 === 0)
    fives.push(v);
});

document.write(fives);

// 具有詞法作用域的 this
var bob = {
  _name: "Bob",
  _friends: ["Amy", "Bob", "Cinne", "Dylan", "Ellen"],
  printFriends() {
    this._friends.forEach(f =>
    document.write(this._name + " knows " + f));
  }
}

bob.printFriends();

箭頭函式有幾個使用注意點。

  • 函式體內的this物件,繫結定義時所在的物件,而不是使用時所在的物件。
  • 不可以當作建構函式,也就是說,不可以使用new命令,否則會丟擲一個錯誤。
  • 不可以使用arguments物件,該物件在函式體內不存在。

上面三點中,第一點尤其值得注意。this物件的指向是可變的,但是在箭頭函式中,它是固定的。

函式繫結

函式繫結運算子是並排的兩個雙引號(::),雙引號左邊是一個物件,右邊是一個函式。該運算子會自動將左邊的物件,作為上下文環境(即this物件),繫結到右邊的函式上面。

let log = ::console.log;
// 等同於
var log = console.log.bind(console);

foo::bar;
// 等同於
bar.call(foo);

foo::bar(...arguments);
i// 等同於
bar.apply(foo, arguments);

尾呼叫優化

什麼是尾呼叫? 尾呼叫的概念非常簡單,一句話就能說清楚,就是指某個函式的最後一步是呼叫另一個函式。

function f(x){
    return g(x);
}

上面程式碼中,函式f的最後一步是呼叫函式g,這就叫尾呼叫。

以下三種情況,都不屬於尾呼叫。

// 情況一
function f(x){
    let y = g(x);
    return y;
}

// 情況二
function f(x){
    return g(x) + 1;
}

// 情況三
function f(x){
    g(x);
}

以上的示例中,情況一、二是呼叫函式g之後,有其他操作。情況三等同於下面的程式碼。

function f(x){
    g(x);
    return undefined;

}

  尾呼叫由於是函式的最後一步操作,所以不需要保留外層函式的呼叫記錄,因為呼叫位置、內部變數等資訊都不會再用到了,只要直接用內層函式的呼叫記錄,取代外層函式的呼叫記錄就可以了。

function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同於
function f() {
  return g(3);
}
f();

// 等同於
g(3);

上面程式碼中,如果函式g不是尾呼叫,函式f就需要儲存內部變數m和n的值、g的呼叫位置等資訊。但由於呼叫g之後,函式f就結束了,所以執行到最後一步,完全可以刪除 f(x) 的呼叫幀,只保留g(3) 的呼叫幀。

“尾呼叫優化”(Tail call optimization),即只保留內層函式的呼叫幀,這樣可以節省記憶體。

同步課程在匯智網可以找到:http://www.hubwiz.com/course/5594e91ac086935f4a6fb8ef/

相關文章