ES6語法 let、const、for...of循環、展開運算符、ES6箭頭函數、箭頭函數和this、模板字面量、解構、對象字面量簡寫法、默認參數函數、super 和 extends、Object.a

 

const 與 let 變量

解構

使用var帶來的麻煩:javascript

function getClothing(isCold) {
  if (isCold) {
    var freezing = 'Grab a jacket!';
  } else {
    var hot = 'It's a shorts kind of day.';
    console.log(freezing);
  }
}

 

運行getClothing(false)後輸出的是undefined,這是由於執行function函數以前,全部變量都會被提高, 提高到函數做用域頂部.java

letconst聲明的變量解決了這種問題,由於他們是塊級做用域, 在代碼塊(用{}表示)中使用letconst聲明變量, 該變量會陷入暫時性死區直到該變量的聲明被處理.git

function getClothing(isCold) {
  if (isCold) {
    const freezing = 'Grab a jacket!';
  } else {
    const hot = 'It's a shorts kind of day.';
    console.log(freezing);
  }
}

  

運行getClothing(false)後輸出的是ReferenceError: freezing is not defined,由於 freezing 沒有在 else 語句、函數做用域或全局做用域內聲明,因此拋出 ReferenceErrorgithub

關於使用letconst規則:spring

  • 使用let聲明的變量能夠從新賦值,可是不能在同一做用域內從新聲明
  • 使用const聲明的變量必須賦值初始化,可是不能在同一做用域類從新聲明也沒法從新賦值.

模板字面量

在ES6以前,將字符串鏈接到一塊兒的方法是+或者concat()方法,如express

const student = {
  name: 'Richard Kalehoff',
  guardian: 'Mr. Kalehoff'
};

const teacher = {
  name: 'Mrs. Wilson',
  room: 'N231'
}

let message = student.name + ' please see ' + teacher.name + ' in ' + teacher.room + ' to pick up your report card.';

 

模板字面量本質上是包含嵌入式表達式的字符串字面量.
模板字面量用倒引號 ( `` )(而不是單引號 ( '' ) 或雙引號( "" ))表示,能夠包含用 ${expression} 表示的佔位符數組

let message = `${student.name} please see ${teacher.name} in ${teacher.room} to pick up your report card.`;

 

解構

在ES6中,可使用解構從數組和對象提取值並賦值給獨特的變量數據結構

解構數組的值:閉包

const point = [10, 25, -34];
const [x, y, z] = point;
console.log(x, y, z);

 

Prints: 10 25 -34app

[]表示被解構的數組, x,y,z表示要將數組中的值存儲在其中的變量, 在解構數組是, 還能夠忽略值, 例如const[x,,z]=point,忽略y座標.

解構對象中的值:

const gemstone = {
  type: 'quartz',
  color: 'rose',
  karat: 21.29
};
const {type, color, karat} = gemstone;
console.log(type, color, karat);

 

花括號 { } 表示被解構的對象,typecolorkarat 表示要將對象中的屬性存儲到其中的變量

對象字面量簡寫法

let type = 'quartz';
let color = 'rose';
let carat = 21.29;

const gemstone = {
  type: type,
  color: color,
  carat: carat
};

console.log(gemstone);

 

使用和所分配的變量名稱相同的名稱初始化對象時若是屬性名稱和所分配的變量名稱同樣,那麼就能夠從對象屬性中刪掉這些重複的變量名稱。

let type = 'quartz';
let color = 'rose';
let carat = 21.29;
const gemstone = {type,color,carat};
console.log(gemstone);
簡寫方法的名稱:

const gemstone = {
  type,
  color,
  carat,
  calculateWorth: function() {
    // 將根據類型(type),顏色(color)和克拉(carat)計算寶石(gemstone)的價值
  }
};
 
   

匿名函數被分配給屬性 calculateWorth,可是真的須要 function 關鍵字嗎?在 ES6 中不須要!

let gemstone = {
  type,
  color,
  carat,
  calculateWorth() { ... }
};

 

for...of循環

for...of循環是最新添加到 JavaScript 循環系列中的循環。
它結合了其兄弟循環形式 for 循環和 for...in 循環的優點,能夠循環任何可迭代(也就是遵照可迭代協議)類型的數據。默認狀況下,包含如下數據類型:StringArrayMapSet,注意不包含 Object 數據類型(即 {})。默認狀況下,對象不可迭代

for循環

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for (let i = 0; i < digits.length; i++) {
  console.log(digits[i]);
}

 

for 循環的最大缺點是須要跟蹤計數器和退出條件。
雖然 for 循環在循環數組時的確具備優點,可是某些數據結構不是數組,所以並不是始終適合使用 loop 循環。

for...in循環

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const index in digits) {
  console.log(digits[index]);
}

 

依然須要使用 index 來訪問數組的值
當你須要向數組中添加額外的方法(或另外一個對象)時,for...in 循環會帶來很大的麻煩。由於 for...in 循環循環訪問全部可枚舉的屬性,意味着若是向數組的原型中添加任何其餘屬性,這些屬性也會出如今循環中。

Array.prototype.decimalfy = function() {
  for (let i = 0; i < this.length; i++) {
    this[i] = this[i].toFixed(2);
  }
};

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const index in digits) {
  console.log(digits[index]);
}

 

forEach 循環 是另外一種形式的 JavaScript 循環。可是,forEach() 其實是數組方法,所以只能用在數組中。也沒法中止或退出 forEach 循環。若是但願你的循環中出現這種行爲,則須要使用基本的 for 循環。

for...of循環
for...of 循環用於循環訪問任何可迭代的數據類型。
for...of 循環的編寫方式和 for...in 循環的基本同樣,只是將 in 替換爲 of,能夠忽略索引。

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const digit of digits) {
  console.log(digit);
}

 

建議使用複數對象名稱來表示多個值的集合。這樣,循環該集合時,可使用名稱的單數版原本表示集合中的單個值。例如,for (const button of buttons) {…}

for...of 循環還具備其餘優點,解決了 for 和 for...in 循環的不足之處。你能夠隨時中止或退出 for...of 循環。

for (const digit of digits) {
  if (digit % 2 === 0) {
    continue;
  }
  console.log(digit);
}

 

不用擔憂向對象中添加新的屬性。for...of 循環將只循環訪問對象中的值。

Array.prototype.decimalfy = function() {
  for (i = 0; i < this.length; i++) {
    this[i] = this[i].toFixed(2);
  }
};

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const digit of digits) {
  console.log(digit);
}

 

展開運算符

展開運算符(用三個連續的點 (...) 表示)是 ES6 中的新概念,使你可以將字面量對象展開爲多個元素

const books = ["Don Quixote", "The Hobbit", "Alice in Wonderland", "Tale of Two Cities"];
console.log(...books);

 

Prints: Don Quixote The Hobbit Alice in Wonderland Tale of Two Cities

展開運算符的一個用途是結合數組。

若是你須要結合多個數組,在有展開運算符以前,必須使用 Arrayconcat() 方法。

const fruits = ["apples", "bananas", "pears"];
const vegetables = ["corn", "potatoes", "carrots"];
const produce = fruits.concat(vegetables);
console.log(produce);

 

Prints: ["apples", "bananas", "pears", "corn", "potatoes", "carrots"]

使用展開符來結合數組

const fruits = ["apples", "bananas", "pears"];
const vegetables = ["corn", "potatoes", "carrots"];
const produce = [...fruits,...vegetables];
console.log(produce);

 

剩餘參數(可變參數)

使用展開運算符將數組展開爲多個元素, 使用剩餘參數能夠將多個元素綁定到一個數組中.
剩餘參數也用三個連續的點 ( ... ) 表示,使你可以將不定數量的元素表示爲數組.

用途1: 將變量賦數組值時:

const order = [20.17, 18.67, 1.50, "cheese", "eggs", "milk", "bread"];
const [total, subtotal, tax, ...items] = order;
console.log(total, subtotal, tax, items);

 

用途2: 可變參數函數
對於參數不固定的函數,ES6以前是使用參數對象(arguments)處理:

function sum() {
  let total = 0;  
  for(const argument of arguments) {
    total += argument;
  }
  return total;
}

 

在ES6中使用剩餘參數運算符則更爲簡潔,可讀性提升:

function sum(...nums) {
  let total = 0;  
  for(const num of nums) {
    total += num;
  }
  return total;
}

 

ES6箭頭函數

ES6以前,使用普通函數把其中每一個名字轉換爲大寫形式:

const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(function(name) { 
  return name.toUpperCase();
});

 

箭頭函數表示:

const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(
  name => name.toUpperCase()
);

 

普通函數能夠是函數聲明或者函數表達式, 可是箭頭函數始終都是表達式, 全程是箭頭函數表達式, 所以所以僅在表達式有效時才能使用,包括:

  • 存儲在變量中,
  • 當作參數傳遞給函數,
  • 存儲在對象的屬性中。
const greet = name => `Hello ${name}!`;
能夠以下調用:

greet('Asser');

 

若是函數的參數只有一個,不須要使用()包起來,可是隻有一個或者多個, 則必須須要將參數列表放在圓括號內:

// 空參數列表須要括號
const sayHi = () => console.log('Hello Udacity Student!');

// 多個參數須要括號
const orderIceCream = (flavor, cone) => console.log(`Here's your ${flavor} ice cream in a ${cone} cone.`);
orderIceCream('chocolate', 'waffle');

 

通常箭頭函數都只有一個表達式做爲函數主題:

const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(
  name => name.toUpperCase()
);

 

這種函數表達式形式稱爲簡寫主體語法:

  • 在函數主體周圍沒有花括號,
  • 自動返回表達式

可是若是箭頭函數的主體內須要多行代碼, 則須要使用常規主體語法:

  • 它將函數主體放在花括號內
  • 須要使用 return 語句來返回內容。
const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map( name => {
  name = name.toUpperCase();
  return `${name} has ${name.length} characters in their name`;
});

 

javascript標準函數this

  1. new 對象
const mySundae = new Sundae('Chocolate', ['Sprinkles', 'Hot Fudge']);
sundae這個構造函數內的this的值是實例對象, 由於他使用new被調用.

 

  1. 指定的對象
const result = obj1.printName.call(obj2);
函數使用call/apply被調用,this的值指向指定的obj2,由於call()第一個參數明確設置this的指向

 

  1. 上下`文對象
data.teleport();
函數是對象的方法, this指向就是那個對象,此處this就是指向data.

 

  1. 全局對象或 undefined
teleport();
此處是this指向全局對象,在嚴格模式下,指向undefined.

 

javascript中this是很複雜的概念, 要詳細判斷this,請參考this豁然開朗

箭頭函數和this

對於普通函數, this的值基於函數如何被調用, 對於箭頭函數,this的值基於函數週圍的上下文, 換句話說,this的值和函數外面的this的值是同樣的.

function IceCream() {
    this.scoops = 0;
}

// 爲 IceCream 添加 addScoop 方法
IceCream.prototype.addScoop = function() {
    setTimeout(function() {
        this.scoops++;
        console.log('scoop added!');
        console.log(this.scoops); // undefined+1=NaN
        console.log(dessert.scoops); //0
    }, 500);
};
---------- 標題 const dessert = new IceCream(); dessert.addScoop();
 
 
   

傳遞給 setTimeout() 的函數被調用時沒用到 newcall()apply(),也沒用到上下文對象。意味着函數內的 this 的值是全局對象,不是 dessert 對象。實際上發生的狀況是,建立了新的 scoops 變量(默認值爲 undefined),而後遞增(undefined + 1 結果爲 NaN);

解決此問題的方式之一是使用閉包(closure):

// 構造函數
function IceCream() {
  this.scoops = 0;
}

// 爲 IceCream 添加 addScoop 方法
IceCream.prototype.addScoop = function() {
  const cone = this; // 設置 `this` 給 `cone`變量
  setTimeout(function() {
    cone.scoops++; // 引用`cone`變量
    console.log('scoop added!'); 
    console.log(dessert.scoops);//1
  }, 0.5);
};

const dessert = new IceCream();
dessert.addScoop();

  

箭頭函數的做用正是如此, 將setTimeOut()的函數改成剪頭函數:

// 構造函數
function IceCream() {
  this.scoops = 0;
}

// 爲 IceCream 添加 addScoop 方法
IceCream.prototype.addScoop = function() {
  setTimeout(() => { // 一個箭頭函數被傳遞給setTimeout
    this.scoops++;
    console.log('scoop added!');
    console.log(dessert.scoops);//1
  }, 0.5);
};

const dessert = new IceCream();
dessert.addScoop();

 

默認參數函數

function greet(name, greeting) {
  name = (typeof name !== 'undefined') ?  name : 'Student';
  greeting = (typeof greeting !== 'undefined') ?  greeting : 'Welcome';

  return `${greeting} ${name}!`;
}

greet(); // Welcome Student!
greet('James'); // Welcome James!
greet('Richard', 'Howdy'); // Howdy Richard!
greet() 函數中混亂的前兩行的做用是什麼?它們的做用是當所需的參數未提供時,爲函數提供默認的值。可是看起來很麻煩, ES6引入一種新的方式建立默認值, 他叫默認函數參數:

function greet(name = 'Student', greeting = 'Welcome') {
  return `${greeting} ${name}!`;
}

greet(); // Welcome Student!
greet('James'); // Welcome James!
greet('Richard', 'Howdy'); // Howdy Richard!

 

默認值與解構

  1. 默認值與解構數組
function createGrid([width = 5, height = 5]) {
  return `Generates a ${width} x ${height} grid`;
}

createGrid([]); // Generates a 5 x 5 grid
createGrid([2]); // Generates a 2 x 5 grid
createGrid([2, 3]); // Generates a 2 x 3 grid
createGrid([undefined, 3]); // Generates a 5 x 3 grid
createGrid() 函數預期傳入的是數組。它經過解構將數組中的第一項設爲 width,第二項設爲 height。若是數組爲空,或者只有一項,那麼就會使用默認參數,並將缺失的參數設爲默認值 5。

可是存在一個問題:

createGrid(); // throws an error

 

Uncaught TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined

出現錯誤,由於 createGrid() 預期傳入的是數組,而後對其進行解構。由於函數被調用時沒有傳入數組,因此出現問題。可是,咱們可使用默認的函數參數!

function createGrid([width = 5, height = 5] = []) {
  return `Generating a grid of ${width} by ${height}`;
}
createGrid(); // Generates a 5 x 5 grid

 

Returns: Generates a 5 x 5 grid

  1. 默認值與解構函數

就像使用數組默認值解構數組同樣,函數可讓對象成爲一個默認參數,並使用對象解構:

function createSundae({scoops = 1, toppings = ['Hot Fudge']}={}) {
  const scoopText = scoops === 1 ? 'scoop' : 'scoops';
  return `Your sundae has ${scoops} ${scoopText} with ${toppings.join(' and ')} toppings.`;
}

createSundae({}); // Your sundae has 1 scoop with Hot Fudge toppings.
createSundae({scoops: 2}); // Your sundae has 2 scoops with Hot Fudge toppings.
createSundae({scoops: 2, toppings: ['Sprinkles']}); // Your sundae has 2 scoops with Sprinkles toppings.
createSundae({toppings: ['Cookie Dough']}); // Your sundae has 1 scoop with Cookie Dough toppings.
createSundae(); // Your sundae has 1 scoop with Hot Fudge toppings.

 

  1. 數組默認值與對象默認值

默認函數參數只是個簡單的添加內容,可是卻帶來不少便利!與數組默認值相比,對象默認值具有的一個優點是可以處理跳過的選項。看看下面的代碼:

function createSundae({scoops = 1, toppings = ['Hot Fudge']} = {}) { … }
在 createSundae() 函數使用對象默認值進行解構時,若是你想使用 scoops 的默認值,可是更改 toppings,那麼只需使用 toppings 傳入一個對象:

createSundae({toppings: ['Hot Fudge', 'Sprinkles', 'Caramel']});
將上述示例與使用數組默認值進行解構的同一函數相對比。

function createSundae([scoops = 1, toppings = ['Hot Fudge']] = []) { … }
對於這個函數,若是想使用 scoops 的默認數量,可是更改 toppings,則必須以這種奇怪的方式調用你的函數:

createSundae([undefined, ['Hot Fudge', 'Sprinkles', 'Caramel']]);
由於數組是基於位置的,咱們須要傳入 undefined 以跳過第一個參數(並使用默認值)來到達第二個參數。

 

Javascript類

ES5建立類:

function Plane(numEngines) {
  this.numEngines = numEngines;
  this.enginesActive = false;
}

// 由全部實例 "繼承" 的方法
Plane.prototype.startEngines = function () {
  console.log('starting engines...');
  this.enginesActive = true;
};

 

ES6類只是一個語法糖,原型繼續實際上在底層隱藏起來, 與傳統類機制語言有些區別.

class Plane {
  //constructor方法雖然在類中,但不是原型上的方法,只是用來生成實例的.
  constructor(numEngines) {
    this.numEngines = numEngines;
    this.enginesActive = false;
  }
  //原型上的方法, 由全部實例對象共享.
  startEngines() {
    console.log('starting engines…');
    this.enginesActive = true;
  }
}

console.log(typeof Plane); //function

 

javascript中類其實只是function, 方法之間不能使用,,不用逗號區分屬性和方法.

靜態方法
要添加靜態方法,請在方法名稱前面加上關鍵字 static

class Plane {
  constructor(numEngines) {
    this.numEngines = numEngines;
    this.enginesActive = false;
  }
  static badWeather(planes) {
    for (plane of planes) {
      plane.enginesActive = false;
    }
  }
  startEngines() {
    console.log('starting engines…');
    this.enginesActive = true;
  }
}

 

  • 關鍵字class帶來其餘基於類的語言的不少思想,可是沒有向javascript中添加此功能
  • javascript類實際上仍是原型繼承
  • 建立javascript類的新實例時必須使用new關鍵字

super 和 extends

使用新的super和extends關鍵字擴展類:

class Tree {
  constructor(size = '10', leaves = {spring: 'green', summer: 'green', fall: 'orange', winter: null}) {
    this.size = size;
    this.leaves = leaves;
    this.leafColor = null;
  }

  changeSeason(season) {
    this.leafColor = this.leaves[season];
    if (season === 'spring') {
      this.size += 1;
    }
  }
}

class Maple extends Tree {
  constructor(syrupQty = 15, size, leaves) {
    super(size, leaves); //super用做函數
    this.syrupQty = syrupQty;
  }

  changeSeason(season) {
    super.changeSeason(season);//super用做對象
    if (season === 'spring') {
      this.syrupQty += 1;
    }
  }

  gatherSyrup() {
    this.syrupQty -= 3;
  }
}
使用ES5編寫一樣功能的類:

function Tree(size, leaves) {
  this.size = size || 10;
  this.leaves = leaves || {spring: 'green', summer: 'green', fall: 'orange', winter: null};
  this.leafColor;
}

Tree.prototype.changeSeason = function(season) {
  this.leafColor = this.leaves[season];
  if (season === 'spring') {
    this.size += 1;
  }
}

function Maple (syrupQty, size, leaves) {
  Tree.call(this, size, leaves);
  this.syrupQty = syrupQty || 15;
}

Maple.prototype = Object.create(Tree.prototype);
Maple.prototype.constructor = Maple;

Maple.prototype.changeSeason = function(season) {
  Tree.prototype.changeSeason.call(this, season);
  if (season === 'spring') {
    this.syrupQty += 1;
  }
}

Maple.prototype.gatherSyrup = function() {
  this.syrupQty -= 3;

 

} 

super 必須在 this 以前被調用

在子類構造函數中,在使用 this 以前,必須先調用超級類。

class Apple {}
class GrannySmith extends Apple {
  constructor(tartnessLevel, energy) {
    this.tartnessLevel = tartnessLevel; // 在 'super' 以前會拋出一個錯誤!
    super(energy); 
  }
}

 

Object.assign()

Object.assign() 方法用於將全部可枚舉屬性的值從一個或多個源對象複製到目標對象。它將返回目標對象。

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }

console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }

 

語法

語法
Object.assign(target, ...sources)
參數
target
目標對象。
sources
源對象。
返回值
目標對象。

描述

若是目標對象中的屬性具備相同的鍵,則屬性將被源對象中的屬性覆蓋。後面的源對象的屬性將相似地覆蓋前面的源對象的屬性。

Object.assign 方法只會拷貝源對象自身的而且可枚舉的屬性到目標對象。該方法使用源對象的[[Get]]和目標對象的[[Set]],因此它會調用相關 getter 和 setter。所以,它分配屬性,而不只僅是複製或定義新的屬性。若是合併源包含getter,這可能使其不適合將新屬性合併到原型中。爲了將屬性定義(包括其可枚舉性)複製到原型,應使用Object.getOwnPropertyDescriptor()和Object.defineProperty() 。

String類型和 Symbol 類型的屬性都會被拷貝。

在出現錯誤的狀況下,例如,若是屬性不可寫,會引起TypeError,若是在引起錯誤以前添加了任何屬性,則能夠更改target對象。

注意,Object.assign 不會跳過那些值爲 null 或 undefined 的源對象。

 

示例

複製一個對象

const obj = { a: 1 };
const copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }

 

深拷貝問題

針對深拷貝,須要使用其餘辦法,由於 Object.assign()拷貝的是屬性值。假如源對象的屬性值是一個對象的引用,那麼它也只指向那個引用。

let obj1 = { a: 0 , b: { c: 0}}; 
let obj2 = Object.assign({}, obj1); 
console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}} 

obj1.a = 1; 
console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 0}} 
console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}} 

obj2.a = 2; 
console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 0}} 
console.log(JSON.stringify(obj2)); // { a: 2, b: { c: 0}}
 
obj2.b.c = 3; 
console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 3}} 
console.log(JSON.stringify(obj2)); // { a: 2, b: { c: 3}} 

// Deep Clone 
obj1 = { a: 0 , b: { c: 0}}; 
let obj3 = JSON.parse(JSON.stringify(obj1)); 
obj1.a = 4; 
obj1.b.c = 4; 
console.log(JSON.stringify(obj3)); // { a: 0, b: { c: 0}}

 

合併對象

const o1 = { a: 1 };
const o2 = { b: 2 };
const o3 = { c: 3 };

const obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1);  // { a: 1, b: 2, c: 3 }, 注意目標對象自身也會改變。

 

合併具備相同屬性的對象

const o1 = { a: 1, b: 1, c: 1 };
const o2 = { b: 2, c: 2 };
const o3 = { c: 3 };

const obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
屬性被後續參數中具備相同屬性的其餘對象覆蓋。

 

拷貝 symbol 類型的屬性

const o1 = { a: 1 };
const o2 = { [Symbol('foo')]: 2 };

const obj = Object.assign({}, o1, o2);
console.log(obj); // { a : 1, [Symbol("foo")]: 2 } (cf. bug 1207182 on Firefox)
Object.getOwnPropertySymbols(obj); // [Symbol(foo)]

 

繼承屬性和不可枚舉屬性是不能拷貝的

const obj = Object.create({foo: 1}, { // foo 是個繼承屬性。
    bar: {
        value: 2  // bar 是個不可枚舉屬性。
    },
    baz: {
        value: 3,
        enumerable: true  // baz 是個自身可枚舉屬性。
    }
});

const copy = Object.assign({}, obj);
console.log(copy); // { baz: 3 }

 

原始類型會被包裝爲對象

const v1 = "abc";
const v2 = true;
const v3 = 10;
const v4 = Symbol("foo")

const obj = Object.assign({}, v1, null, v2, undefined, v3, v4); 
// 原始類型會被包裝,null 和 undefined 會被忽略。
// 注意,只有字符串的包裝對象纔可能有自身可枚舉屬性。
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

 

異常會打斷後續拷貝任務

const target = Object.defineProperty({}, "foo", {
    value: 1,
    writable: false
}); // target 的 foo 屬性是個只讀屬性。

Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4});
// TypeError: "foo" is read-only
// 注意這個異常是在拷貝第二個源對象的第二個屬性時發生的。

console.log(target.bar);  // 2,說明第一個源對象拷貝成功了。
console.log(target.foo2); // 3,說明第二個源對象的第一個屬性也拷貝成功了。
console.log(target.foo);  // 1,只讀屬性不能被覆蓋,因此第二個源對象的第二個屬性拷貝失敗了。
console.log(target.foo3); // undefined,異常以後 assign 方法就退出了,第三個屬性是不會被拷貝到的。
console.log(target.baz);  // undefined,第三個源對象更是不會被拷貝到的。

 

拷貝訪問器

const obj = {
  foo: 1,
  get bar() {
    return 2;
  }
};

let copy = Object.assign({}, obj); 
console.log(copy); // { foo: 1, bar: 2 } copy.bar的值來自obj.bar的getter函數的返回值

// 下面這個函數會拷貝全部自有屬性的屬性描述符
function completeAssign(target, ...sources) {
  sources.forEach(source => {
    let descriptors = Object.keys(source).reduce((descriptors, key) => {
      descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
      return descriptors;
    }, {});

    // Object.assign 默認也會拷貝可枚舉的Symbols
    Object.getOwnPropertySymbols(source).forEach(sym => {
      let descriptor = Object.getOwnPropertyDescriptor(source, sym);
      if (descriptor.enumerable) {
        descriptors[sym] = descriptor;
      }
    });
    Object.defineProperties(target, descriptors);
  });
  return target;
}

copy = completeAssign({}, obj);
console.log(copy);
// { foo:1, get bar() { return 2 } }

 

Polyfill

polyfill不支持 symbol 屬性,由於ES5 中根本沒有 symbol :

if (typeof Object.assign != 'function') {
  // Must be writable: true, enumerable: false, configurable: true
  Object.defineProperty(Object, "assign", {
    value: function assign(target, varArgs) { // .length of function is 2
      'use strict';
      if (target == null) { // TypeError if undefined or null
        throw new TypeError('Cannot convert undefined or null to object');
      }

      let to = Object(target);

      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource != null) { // Skip over if undefined or null
          for (let nextKey in nextSource) {
            // Avoid bugs when hasOwnProperty is shadowed
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}
相關文章
相關標籤/搜索