成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

ES6 變量聲明與賦值:值傳遞、淺拷貝與深拷貝詳解

snowLu / 2418人閱讀

摘要:變量聲明與賦值值傳遞淺拷貝與深拷貝詳解歸納于筆者的現(xiàn)代開(kāi)發(fā)語(yǔ)法基礎(chǔ)與實(shí)踐技巧系列文章。變量聲明在中,基本的變量聲明可以用方式允許省略,直接對(duì)未聲明的變量賦值。按值傳遞中函數(shù)的形參是被調(diào)用時(shí)所傳實(shí)參的副本。

ES6 變量聲明與賦值:值傳遞、淺拷貝與深拷貝詳解歸納于筆者的現(xiàn)代 JavaScript 開(kāi)發(fā):語(yǔ)法基礎(chǔ)與實(shí)踐技巧系列文章。本文首先介紹 ES6 中常用的三種變量聲明方式,然后討論了 JavaScript 按值傳遞的特性,最后介紹了復(fù)合類(lèi)型拷貝的技巧;有興趣的可以閱讀下一章節(jié) ES6 變量作用域與提升:變量的生命周期詳解。

變量聲明與賦值

ES6 為我們引入了 let 與 const 兩種新的變量聲明關(guān)鍵字,同時(shí)也引入了塊作用域;本文首先介紹 ES6 中常用的三種變量聲明方式,然后討論了 JavaScript 按值傳遞的特性以及多種的賦值方式,最后介紹了復(fù)合類(lèi)型拷貝的技巧。

變量聲明

在 JavaScript 中,基本的變量聲明可以用 var 方式;JavaScript 允許省略 var,直接對(duì)未聲明的變量賦值。也就是說(shuō),var a = 1a = 1,這兩條語(yǔ)句的效果相同。但是由于這樣的做法很容易不知不覺(jué)地創(chuàng)建全局變量(尤其是在函數(shù)內(nèi)部),所以建議總是使用 var 命令聲明變量。在 ES6 中,對(duì)于變量聲明的方式進(jìn)行了擴(kuò)展,引入了 let 與 const。var 與 let 兩個(gè)關(guān)鍵字創(chuàng)建變量的區(qū)別在于, var 聲明的變量作用域是最近的函數(shù)塊;而 let 聲明的變量作用域是最近的閉合塊,往往會(huì)小于函數(shù)塊。另一方面,以 let 關(guān)鍵字創(chuàng)建的變量雖然同樣被提升到作用域頭部,但是并不能在實(shí)際聲明前使用;如果強(qiáng)行使用則會(huì)拋出 ReferenceError 異常。

var

var 是 JavaScript 中基礎(chǔ)的變量聲明方式之一,其基本語(yǔ)法為:

var x; // Declaration and initialization
x = "Hello World"; // Assignment

// Or all in one
var y = "Hello World";

ECMAScript 6 以前我們?cè)?JavaScript 中并沒(méi)有其他的變量聲明方式,以 var 聲明的變量作用于函數(shù)作用域中,如果沒(méi)有相應(yīng)的閉合函數(shù)作用域,那么該變量會(huì)被當(dāng)做默認(rèn)的全局變量進(jìn)行處理。

function sayHello(){
  var hello = "Hello World";
  return hello;
}
console.log(hello);

像如上這種調(diào)用方式會(huì)拋出異常: ReferenceError: hello is not defined,因?yàn)?hello 變量只能作用于 sayHello 函數(shù)中,不過(guò)如果按照如下先聲明全局變量方式再使用時(shí),其就能夠正常調(diào)用:

var hello = "Hello World";
function sayHello(){
  return hello;
}
console.log(hello);
let

在 ECMAScript 6 中我們可以使用 let 關(guān)鍵字進(jìn)行變量聲明:

let x; // Declaration and initialization
x = "Hello World"; // Assignment

// Or all in one
let y = "Hello World";

let 關(guān)鍵字聲明的變量是屬于塊作用域,也就是包含在 {} 之內(nèi)的作用于。使用 let 關(guān)鍵字的優(yōu)勢(shì)在于能夠降低偶然的錯(cuò)誤的概率,因?yàn)槠浔WC了每個(gè)變量只能在最小的作用域內(nèi)進(jìn)行訪問(wèn)。

var name = "Peter";
if(name === "Peter"){
  let hello = "Hello Peter";
} else {
  let hello = "Hi";
}
console.log(hello);

上述代碼同樣會(huì)拋出 ReferenceError: hello is not defined 異常,因?yàn)?hello 只能夠在閉合的塊作用域中進(jìn)行訪問(wèn),我們可以進(jìn)行如下修改:

var name = "Peter";
if(name === "Peter"){
  let hello = "Hello Peter";
  console.log(hello);
} else {
  let hello = "Hi";
  console.log(hello);
}

我們可以利用這種塊級(jí)作用域的特性來(lái)避免閉包中因?yàn)樽兞勘A舳鴮?dǎo)致的問(wèn)題,譬如如下兩種異步代碼,使用 var 時(shí)每次循環(huán)中使用的都是相同變量;而使用 let 聲明的 i 則會(huì)在每次循環(huán)時(shí)進(jìn)行不同的綁定,即每次循環(huán)中閉包捕獲的都是不同的 i 實(shí)例:

for(let i = 0;i < 2; i++){
        setTimeout(()=>{console.log(`i:${i}`)},0);
}

for(var j = 0;j < 2; j++){
        setTimeout(()=>{console.log(`j:${j}`)},0);
}

let k = 0;
for(k = 0;k < 2; k++){
        setTimeout(()=>{console.log(`k:${k}`)},0);
}

// output
i:0
i:1
j:2
j:2
k:2
k:2
const

const 關(guān)鍵字一般用于常量聲明,用 const 關(guān)鍵字聲明的常量需要在聲明時(shí)進(jìn)行初始化并且不可以再進(jìn)行修改,并且 const 關(guān)鍵字聲明的常量被限制于塊級(jí)作用域中進(jìn)行訪問(wèn)。

function f() {
  {
    let x;
    {
      // okay, block scoped name
      const x = "sneaky";
      // error, const
      x = "foo";
    }
    // error, already declared in block
    let x = "inner";
  }
}

JavaScript 中 const 關(guān)鍵字的表現(xiàn)于 C 中存在著一定差異,譬如下述使用方式在 JavaScript 中就是正確的,而在 C 中則拋出異常:

# JavaScript
const numbers = [1, 2, 3, 4, 6]
numbers[4] = 5
console.log(numbers[4]) // print 5 

# C
const int numbers[] = {1, 2, 3, 4, 6};
numbers[4] = 5; // error: read-only variable is not assignable
printf("%d
", numbers[4]); 

從上述對(duì)比我們也可以看出,JavaScript 中 const 限制的并非值不可變性;而是創(chuàng)建了不可變的綁定,即對(duì)于某個(gè)值的只讀引用,并且禁止了對(duì)于該引用的重賦值,即如下的代碼會(huì)觸發(fā)錯(cuò)誤:

const numbers = [1, 2, 3, 4, 6]
numbers = [7, 8, 9, 10, 11] // error: assignment to constant variable
console.log(numbers[4])

我們可以參考如下圖片理解這種機(jī)制,每個(gè)變量標(biāo)識(shí)符都會(huì)關(guān)聯(lián)某個(gè)存放變量實(shí)際值的物理地址;所謂只讀的變量即是該變量標(biāo)識(shí)符不可以被重新賦值,而該變量指向的值還是可變的。

JavaScript 中存在著所謂的原始類(lèi)型與復(fù)合類(lèi)型,使用 const 聲明的原始類(lèi)型是值不可變的:

# Example 1
const a = 10
a = a + 1 // error: assignment to constant variable
# Example 2
const isTrue = true
isTrue = false // error: assignment to constant variable
# Example 3
const sLower = "hello world"
const sUpper = sLower.toUpperCase() // create a new string
console.log(sLower) // print hello world
console.log(sUpper) // print HELLO WORLD

而如果我們希望將某個(gè)對(duì)象同樣變成不可變類(lèi)型,則需要使用 Object.freeze();不過(guò)該方法僅對(duì)于鍵值對(duì)的 Object 起作用,而無(wú)法作用于 Date、Map 與 Set 等類(lèi)型:

# Example 4
const me = Object.freeze({name: “Jacopo”})
me.age = 28
console.log(me.age) // print undefined
# Example 5
const arr = Object.freeze([-1, 1, 2, 3])
arr[0] = 0
console.log(arr[0]) // print -1
# Example 6
const me = Object.freeze({
  name: "Jacopo", 
  pet: {
    type: "dog",
    name: "Spock"
  }
})
me.pet.name = "Rocky"
me.pet.breed = "German Shepherd"
console.log(me.pet.name) // print Rocky
console.log(me.pet.breed) // print German Shepherd

即使是 Object.freeze() 也只能防止頂層屬性被修改,而無(wú)法限制對(duì)于嵌套屬性的修改,這一點(diǎn)我們會(huì)在下文的淺拷貝與深拷貝部分繼續(xù)討論。

變量賦值 按值傳遞

JavaScript 中永遠(yuǎn)是按值傳遞(pass-by-value),只不過(guò)當(dāng)我們傳遞的是某個(gè)對(duì)象的引用時(shí),這里的值指的是對(duì)象的引用。按值傳遞中函數(shù)的形參是被調(diào)用時(shí)所傳實(shí)參的副本。修改形參的值并不會(huì)影響實(shí)參。而按引用傳遞(pass-by-reference)時(shí),函數(shù)的形參接收實(shí)參的隱式引用,而不再是副本。這意味著函數(shù)形參的值如果被修改,實(shí)參也會(huì)被修改。同時(shí)兩者指向相同的值。我們首先看下 C 中按值傳遞與引用傳遞的區(qū)別:

void Modify(int p, int * q)
{
    p = 27; // 按值傳遞 - p是實(shí)參a的副本, 只有p被修改
    *q = 27; // q是b的引用,q和b都被修改
}
int main()
{
    int a = 1;
    int b = 1;
    Modify(a, &b);   // a 按值傳遞, b 按引用傳遞,
                     // a 未變化, b 改變了
    return(0);
}

而在 JavaScript 中,對(duì)比例子如下:

function changeStuff(a, b, c)
{
  a = a * 10;
  b.item = "changed";
  c = {item: "changed"};
}

var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};

changeStuff(num, obj1, obj2);

console.log(num);
console.log(obj1.item);    
console.log(obj2.item);

// 輸出結(jié)果
10
changed
unchanged

JavaScript 按值傳遞就表現(xiàn)于在內(nèi)部修改了 c 的值但是并不會(huì)影響到外部的 obj2 變量。如果我們更深入地來(lái)理解這個(gè)問(wèn)題,JavaScript 對(duì)于對(duì)象的傳遞則是按共享傳遞的(pass-by-sharing,也叫按對(duì)象傳遞、按對(duì)象共享傳遞)。最早由Barbara Liskov. 在1974年的GLU語(yǔ)言中提出;該求值策略被用于Python、Java、Ruby、JS等多種語(yǔ)言。該策略的重點(diǎn)是:調(diào)用函數(shù)傳參時(shí),函數(shù)接受對(duì)象實(shí)參引用的副本(既不是按值傳遞的對(duì)象副本,也不是按引用傳遞的隱式引用)。 它和按引用傳遞的不同在于:在共享傳遞中對(duì)函數(shù)形參的賦值,不會(huì)影響實(shí)參的值。按共享傳遞的直接表現(xiàn)就是上述代碼中的 obj1,當(dāng)我們?cè)诤瘮?shù)內(nèi)修改了 b 指向的對(duì)象的屬性值時(shí),我們使用 obj1 來(lái)訪問(wèn)相同的變量時(shí)同樣會(huì)得到變化后的值。

連續(xù)賦值

JavaScript 中是支持變量的連續(xù)賦值,即譬如:

var a=b=1;

但是在連續(xù)賦值中,會(huì)發(fā)生引用保留,可以考慮如下情景:

var a = {n:1};  
a.x = a = {n:2};  
alert(a.x); // --> undefined  

為了解釋上述問(wèn)題,我們引入一個(gè)新的變量:

var a = {n:1};  
var b = a; // 持有a,以回查  
a.x = a = {n:2};  
alert(a.x);// --> undefined  
alert(b.x);// --> [object Object]  

實(shí)際上在連續(xù)賦值中,值是直接賦予給變量指向的內(nèi)存地址:

              a.x  =  a  = {n:2}
              │      │
      {n:1}<──┘      └─>{n:2}
Deconstruction: 解構(gòu)賦值

解構(gòu)賦值允許你使用類(lèi)似數(shù)組或?qū)ο笞置媪康恼Z(yǔ)法將數(shù)組和對(duì)象的屬性賦給各種變量。這種賦值語(yǔ)法極度簡(jiǎn)潔,同時(shí)還比傳統(tǒng)的屬性訪問(wèn)方法更為清晰。傳統(tǒng)的訪問(wèn)數(shù)組前三個(gè)元素的方式為:

    var first = someArray[0];
    var second = someArray[1];
    var third = someArray[2];

而通過(guò)解構(gòu)賦值的特性,可以變?yōu)椋?/p>

    var [first, second, third] = someArray;
// === Arrays

var [a, b] = [1, 2];
console.log(a, b);
//=> 1 2


// Use from functions, only select from pattern
var foo = () => {
  return [1, 2, 3];
};

var [a, b] = foo();
console.log(a, b);
// => 1 2


// Omit certain values
var [a, , b] = [1, 2, 3];
console.log(a, b);
// => 1 3


// Combine with spread/rest operator (accumulates the rest of the values)
var [a, ...b] = [1, 2, 3];
console.log(a, b);
// => 1 [ 2, 3 ]


// Fail-safe.
var [, , , a, b] = [1, 2, 3];
console.log(a, b);
// => undefined undefined


// Swap variables easily without temp
var a = 1, b = 2;
[b, a] = [a, b];
console.log(a, b);
// => 2 1


// Advance deep arrays
var [a, [b, [c, d]]] = [1, [2, [[[3, 4], 5], 6]]];
console.log("a:", a, "b:", b, "c:", c, "d:", d);
// => a: 1 b: 2 c: [ [ 3, 4 ], 5 ] d: 6


// === Objects

var {user: x} = {user: 5};
console.log(x);
// => 5


// Fail-safe
var {user: x} = {user2: 5};
console.log(x);
// => undefined


// More values
var {prop: x, prop2: y} = {prop: 5, prop2: 10};
console.log(x, y);
// => 5 10

// Short-hand syntax
var { prop, prop2} = {prop: 5, prop2: 10};
console.log(prop, prop2);
// => 5 10

// Equal to:
var { prop: prop, prop2: prop2} = {prop: 5, prop2: 10};
console.log(prop, prop2);
// => 5 10

// Oops: This doesn"t work:
var a, b;
{ a, b } = {a: 1, b: 2};

// But this does work
var a, b;
({ a, b } = {a: 1, b: 2});
console.log(a, b);
// => 1 2

// This due to the grammar in JS. 
// Starting with { implies a block scope, not an object literal. 
// () converts to an expression.

// From Harmony Wiki:
// Note that object literals cannot appear in
// statement positions, so a plain object
// destructuring assignment statement
//  { x } = y must be parenthesized either
// as ({ x } = y) or ({ x }) = y.

// Combine objects and arrays
var {prop: x, prop2: [, y]} = {prop: 5, prop2: [10, 100]};
console.log(x, y);
// => 5 100


// Deep objects
var {
  prop: x,
  prop2: {
    prop2: {
      nested: [ , , b]
    }
  }
} = { prop: "Hello", prop2: { prop2: { nested: ["a", "b", "c"]}}};
console.log(x, b);
// => Hello c


// === Combining all to make fun happen

// All well and good, can we do more? Yes!
// Using as method parameters
var foo = function ({prop: x}) {
  console.log(x);
};

foo({invalid: 1});
foo({prop: 1});
// => undefined
// => 1


// Can also use with the advanced example
var foo = function ({
  prop: x,
  prop2: {
    prop2: {
      nested: b
    }
  }
}) {
  console.log(x, ...b);
};
foo({ prop: "Hello", prop2: { prop2: { nested: ["a", "b", "c"]}}});
// => Hello a b c


// In combination with other ES2015 features.

// Computed property names
const name = "fieldName";
const computedObject = { [name]: name }; // (where object is { "fieldName": "fieldName" })
const { [name]: nameValue } = computedObject;
console.log(nameValue)
// => fieldName



// Rest and defaults
var ajax = function ({ url = "localhost", port: p = 80}, ...data) {
  console.log("Url:", url, "Port:", p, "Rest:", data);
};

ajax({ url: "someHost" }, "additional", "data", "hello");
// => Url: someHost Port: 80 Rest: [ "additional", "data", "hello" ]

ajax({ }, "additional", "data", "hello");
// => Url: localhost Port: 80 Rest: [ "additional", "data", "hello" ]


// Ooops: Doesn"t work (in traceur)
var ajax = ({ url = "localhost", port: p = 80}, ...data) => {
  console.log("Url:", url, "Port:", p, "Rest:", data);
};
ajax({ }, "additional", "data", "hello");
// probably due to traceur compiler

But this does:
var ajax = ({ url: url = "localhost", port: p = 80}, ...data) => {
  console.log("Url:", url, "Port:", p, "Rest:", data);
};
ajax({ }, "additional", "data", "hello");


// Like _.pluck
var users = [
  { user: "Name1" },
  { user: "Name2" },
  { user: "Name2" },
  { user: "Name3" }
];
var names = users.map( ({ user }) => user );
console.log(names);
// => [ "Name1", "Name2", "Name2", "Name3" ]


// Advanced usage with Array Comprehension and default values
var users = [
  { user: "Name1" },
  { user: "Name2", age: 2 },
  { user: "Name2" },
  { user: "Name3", age: 4 }
];

[for ({ user, age = "DEFAULT AGE" } of users) console.log(user, age)];
// => Name1 DEFAULT AGE
// => Name2 2
// => Name2 DEFAULT AGE
// => Name3 4
數(shù)組與迭代器

以上是數(shù)組解構(gòu)賦值的一個(gè)簡(jiǎn)單示例,其語(yǔ)法的一般形式為:

    [ variable1, variable2, ..., variableN ] = array;

這將為variable1到variableN的變量賦予數(shù)組中相應(yīng)元素項(xiàng)的值。如果你想在賦值的同時(shí)聲明變量,可在賦值語(yǔ)句前加入var、letconst關(guān)鍵字,例如:

    var [ variable1, variable2, ..., variableN ] = array;
    let [ variable1, variable2, ..., variableN ] = array;
    const [ variable1, variable2, ..., variableN ] = array;

事實(shí)上,用變量來(lái)描述并不恰當(dāng),因?yàn)槟憧梢詫?duì)任意深度的嵌套數(shù)組進(jìn)行解構(gòu):

    var [foo, [[bar], baz]] = [1, [[2], 3]];
    console.log(foo);
    // 1
    console.log(bar);
    // 2
    console.log(baz);
    // 3

此外,你可以在對(duì)應(yīng)位留空來(lái)跳過(guò)被解構(gòu)數(shù)組中的某些元素:

    var [,,third] = ["foo", "bar", "baz"];
    console.log(third);
    // "baz"

而且你還可以通過(guò)“不定參數(shù)”模式捕獲數(shù)組中的所有尾隨元素:

    var [head, ...tail] = [1, 2, 3, 4];
    console.log(tail);
    // [2, 3, 4]

當(dāng)訪問(wèn)空數(shù)組或越界訪問(wèn)數(shù)組時(shí),對(duì)其解構(gòu)與對(duì)其索引的行為一致,最終得到的結(jié)果都是:undefined。

    console.log([][0]);
    // undefined
    var [missing] = [];
    console.log(missing);
    // undefined

請(qǐng)注意,數(shù)組解構(gòu)賦值的模式同樣適用于任意迭代器:

    function* fibs() {
      var a = 0;
      var b = 1;
      while (true) {
        yield a;
        [a, b] = [b, a + b];
      }
    }
    var [first, second, third, fourth, fifth, sixth] = fibs();
    console.log(sixth);
    // 5
對(duì)象

通過(guò)解構(gòu)對(duì)象,你可以把它的每個(gè)屬性與不同的變量綁定,首先指定被綁定的屬性,然后緊跟一個(gè)要解構(gòu)的變量。

    var robotA = { name: "Bender" };
    var robotB = { name: "Flexo" };
    var { name: nameA } = robotA;
    var { name: nameB } = robotB;
    console.log(nameA);
    // "Bender"
    console.log(nameB);
    // "Flexo"

當(dāng)屬性名與變量名一致時(shí),可以通過(guò)一種實(shí)用的句法簡(jiǎn)寫(xiě):

    var { foo, bar } = { foo: "lorem", bar: "ipsum" };
    console.log(foo);
    // "lorem"
    console.log(bar);
    // "ipsum"

與數(shù)組解構(gòu)一樣,你可以隨意嵌套并進(jìn)一步組合對(duì)象解構(gòu):

    var complicatedObj = {
      arrayProp: [
        "Zapp",
        { second: "Brannigan" }
      ]
    };
    var { arrayProp: [first, { second }] } = complicatedObj;
    console.log(first);
    // "Zapp"
    console.log(second);
    // "Brannigan"

當(dāng)你解構(gòu)一個(gè)未定義的屬性時(shí),得到的值為undefined

    var { missing } = {};
    console.log(missing);
    // undefined

請(qǐng)注意,當(dāng)你解構(gòu)對(duì)象并賦值給變量時(shí),如果你已經(jīng)聲明或不打算聲明這些變量(亦即賦值語(yǔ)句前沒(méi)有let、constvar關(guān)鍵字),你應(yīng)該注意這樣一個(gè)潛在的語(yǔ)法錯(cuò)誤:

    { blowUp } = { blowUp: 10 };
    // Syntax error 語(yǔ)法錯(cuò)誤

為什么會(huì)出錯(cuò)?這是因?yàn)镴avaScript語(yǔ)法通知解析引擎將任何以{開(kāi)始的語(yǔ)句解析為一個(gè)塊語(yǔ)句(例如,{console}是一個(gè)合法塊語(yǔ)句)。解決方案是將整個(gè)表達(dá)式用一對(duì)小括號(hào)包裹:

    ({ safe } = {});
    // No errors 沒(méi)有語(yǔ)法錯(cuò)誤
默認(rèn)值

當(dāng)你要解構(gòu)的屬性未定義時(shí)你可以提供一個(gè)默認(rèn)值:

    var [missing = true] = [];
    console.log(missing);
    // true
    var { message: msg = "Something went wrong" } = {};
    console.log(msg);
    // "Something went wrong"
    var { x = 3 } = {};
    console.log(x);
    // 3

由于解構(gòu)中允許對(duì)對(duì)象進(jìn)行解構(gòu),并且還支持默認(rèn)值,那么完全可以將解構(gòu)應(yīng)用在函數(shù)參數(shù)以及參數(shù)的默認(rèn)值中。

function removeBreakpoint({ url, line, column }) {
      // ...
    }

當(dāng)我們構(gòu)造一個(gè)提供配置的對(duì)象,并且需要這個(gè)對(duì)象的屬性攜帶默認(rèn)值時(shí),解構(gòu)特性就派上用場(chǎng)了。舉個(gè)例子,jQuery的ajax函數(shù)使用一個(gè)配置對(duì)象作為它的第二參數(shù),我們可以這樣重寫(xiě)函數(shù)定義:

jQuery.ajax = function (url, {
      async = true,
      beforeSend = noop,
      cache = true,
      complete = noop,
      crossDomain = false,
      global = true,
      // ... 更多配置
    }) {
      // ... do stuff
    };

同樣,解構(gòu)也可以應(yīng)用在函數(shù)的多重返回值中,可以類(lèi)似于其他語(yǔ)言中的元組的特性:

function returnMultipleValues() {
      return [1, 2];
    }
var [foo, bar] = returnMultipleValues();
Three Dots Rest Operator

在 JavaScript 函數(shù)調(diào)用時(shí)我們往往會(huì)使用內(nèi)置的 arguments 對(duì)象來(lái)獲取函數(shù)的調(diào)用參數(shù),不過(guò)這種方式卻存在著很多的不方便性。譬如 arguments 對(duì)象是 Array-Like 對(duì)象,無(wú)法直接運(yùn)用數(shù)組的 .map() 或者 .forEach() 函數(shù);并且因?yàn)?arguments 是綁定于當(dāng)前函數(shù)作用域,如果我們希望在嵌套函數(shù)里使用外層函數(shù)的 arguments 對(duì)象,我們還需要?jiǎng)?chuàng)建中間變量。

function outerFunction() {  
   // store arguments into a separated variable
   var argsOuter = arguments;
   function innerFunction() {
      // args is an array-like object
      var even = Array.prototype.map.call(argsOuter, function(item) {
         // do something with argsOuter               
      });
   }
}

ES6 中為我們提供了 Rest Operator 來(lái)以數(shù)組形式獲取函數(shù)的調(diào)用參數(shù),Rest Operator 也可以用于在解構(gòu)賦值中以數(shù)組方式獲取剩余的變量:

function countArguments(...args) {  
   return args.length;
}
// get the number of arguments
countArguments("welcome", "to", "Earth"); // => 3  
// destructure an array
let otherSeasons, autumn;  
[autumn, ...otherSeasons] = cold;
otherSeasons      // => ["winter"]  

典型的 Rest Operator 的應(yīng)用場(chǎng)景譬如進(jìn)行不定數(shù)組的指定類(lèi)型過(guò)濾:

function filter(type, ...items) {  
  return items.filter(item => typeof item === type);
}
filter("boolean", true, 0, false);        // => [true, false]  
filter("number", false, 4, "Welcome", 7); // => [4, 7]  

盡管 Arrow Function 中并沒(méi)有定義 arguments 對(duì)象,但是我們?nèi)匀豢梢允褂?Rest Operator 來(lái)獲取 Arrow Function 的調(diào)用參數(shù):

(function() {
  let outerArguments = arguments;
  const concat = (...items) => {
    console.log(arguments === outerArguments); // => true
    return items.reduce((result, item) => result + item, "");
  };
  concat(1, 5, "nine"); // => "15nine"
})();
Spread Operator

Spread Operator 則與 Rest Opeator 的功能正好相反,其常用于進(jìn)行數(shù)組構(gòu)建與解構(gòu)賦值,也可以用于將某個(gè)數(shù)組轉(zhuǎn)化為函數(shù)的參數(shù)列表,其基本使用方式如下:

let cold = ["autumn", "winter"];  
let warm = ["spring", "summer"];  
// construct an array
[...cold, ...warm] // => ["autumn", "winter", "spring", "summer"]
// function arguments from an array
cold.push(...warm);  
cold              // => ["autumn", "winter", "spring", "summer"]  

我們也可以使用 Spread Operator 來(lái)簡(jiǎn)化函數(shù)調(diào)用:

class King {  
   constructor(name, country) {
     this.name = name;
     this.country = country;     
   }
   getDescription() {
     return `${this.name} leads ${this.country}`;
   }
}
var details = ["Alexander the Great", "Greece"];  
var Alexander = new King(...details);  
Alexander.getDescription(); // => "Alexander the Great leads Greece"  

還有另外一個(gè)好處就是可以用來(lái)替換 Object.assign 來(lái)方便地從舊有的對(duì)象中創(chuàng)建新的對(duì)象,并且能夠修改部分值;譬如:

var obj = {a:1,b:2}
var obj_new_1 = Object.assign({},obj,{a:3});
var obj_new_2 = {
  ...obj,
  a:3
}

最后我們還需要討論下 Spread Operator 與 Iteration Protocols,實(shí)際上 Spread Operator 也是使用的 Iteration Protocols 來(lái)進(jìn)行元素遍歷與結(jié)果搜集;因此我們也可以通過(guò)自定義 Iterator 的方式來(lái)控制 Spread Operator 的表現(xiàn)。Iterable 協(xié)議規(guī)定了對(duì)象必須包含 Symbol.iterator 方法,該方法返回某個(gè) Iterator 對(duì)象:

interface Iterable {  
  [Symbol.iterator]() {
    //...
    return Iterator;
  }
}

該 Iterator 對(duì)象從屬于 Iterator Protocol,其需要提供 next 成員方法,該方法會(huì)返回某個(gè)包含 done 與 value 屬性的對(duì)象:

interface Iterator {  
  next() {
     //...
     return {
        value: ,
        done: 
     };
  };
}

典型的 Iterable 對(duì)象就是字符串:

var str = "hi";  
var iterator = str[Symbol.iterator]();  
iterator.toString(); // => "[object String Iterator]"  
iterator.next();     // => { value: "h", done: false }  
iterator.next();     // => { value: "i", done: false }  
iterator.next();     // => { value: undefined, done: true }  
[...str];            // => ["h", "i"]

我們可以通過(guò)自定義 array-like 對(duì)象的 Symbol.iterator 屬性來(lái)控制其在迭代器上的效果:

function iterator() {  
  var index = 0;
  return {
    next: () => ({ // Conform to Iterator protocol
      done : index >= this.length,
      value: this[index++]
    })
  };
}
var arrayLike = {  
  0: "Cat",
  1: "Bird",
  length: 2
};
// Conform to Iterable Protocol
arrayLike[Symbol.iterator] = iterator;  
var array = [...arrayLike];  
console.log(array); // => ["Cat", "Bird"]  

arrayLike[Symbol.iterator] 為該對(duì)象創(chuàng)建了值為某個(gè)迭代器的屬性,從而使該對(duì)象符合了 Iterable 協(xié)議;而 iterator() 又返回了包含 next 成員方法的對(duì)象,使得該對(duì)象最終具有和數(shù)組相似的行為表現(xiàn)。

Copy Composite Data Types: 復(fù)合類(lèi)型的拷貝 Shallow Copy: 淺拷貝 頂層屬性遍歷

淺拷貝是指復(fù)制對(duì)象的時(shí)候,指對(duì)第一層鍵值對(duì)進(jìn)行獨(dú)立的復(fù)制。一個(gè)簡(jiǎn)單的實(shí)現(xiàn)如下:

// 淺拷貝實(shí)現(xiàn)
function shadowCopy(target, source){ 
    if( !source || typeof source !== "object"){
        return;
    }
    // 這個(gè)方法有點(diǎn)小trick,target一定得事先定義好,不然就不能改變實(shí)參了。
       // 具體原因解釋可以看參考資料中 JS是值傳遞還是引用傳遞
    if( !target || typeof target !== "object"){
        return;
    }  
    // 這邊最好區(qū)別一下對(duì)象和數(shù)組的復(fù)制
    for(var key in source){
        if(source.hasOwnProperty(key)){
            target[key] = source[key];
        }
    }
}

//測(cè)試?yán)?var arr = [1,2,3];
var arr2 = [];
shadowCopy(arr2, arr);
console.log(arr2);
//[1,2,3]

var today = {
    weather: "Sunny",
    date: {
        week: "Wed"
    } 
}

var tomorrow = {};
shadowCopy(tomorrow, today);
console.log(tomorrow);
// Object {weather: "Sunny", date: Object}
Object.assign

Object.assign() 方法可以把任意多個(gè)的源對(duì)象所擁有的自身可枚舉屬性拷貝給目標(biāo)對(duì)象,然后返回目標(biāo)對(duì)象。Object.assign 方法只會(huì)拷貝源對(duì)象自身的并且可枚舉的屬性到目標(biāo)對(duì)象身上。注意,對(duì)于訪問(wèn)器屬性,該方法會(huì)執(zhí)行那個(gè)訪問(wèn)器屬性的 getter 函數(shù),然后把得到的值拷貝給目標(biāo)對(duì)象,如果你想拷貝訪問(wèn)器屬性本身,請(qǐng)使用 Object.getOwnPropertyDescriptor()Object.defineProperties() 方法。

注意,字符串類(lèi)型和 symbol 類(lèi)型的屬性都會(huì)被拷貝。

注意,在屬性拷貝過(guò)程中可能會(huì)產(chǎn)生異常,比如目標(biāo)對(duì)象的某個(gè)只讀屬性和源對(duì)象的某個(gè)屬性同名,這時(shí)該方法會(huì)拋出一個(gè) TypeError 異常,拷貝過(guò)程中斷,已經(jīng)拷貝成功的屬性不會(huì)受到影響,還未拷貝的屬性將不會(huì)再被拷貝。

注意, Object.assign 會(huì)跳過(guò)那些值為 nullundefined 的源對(duì)象。

Object.assign(target, ...sources)

例子:淺拷貝一個(gè)對(duì)象

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

例子:合并若干個(gè)對(duì)象

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

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

例子:拷貝 symbol 類(lèi)型的屬性

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

var obj = Object.assign({}, o1, o2);
console.log(obj); // { a: 1, [Symbol("foo")]: 2 }

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

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

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

例子:原始值會(huì)被隱式轉(zhuǎn)換成其包裝對(duì)象

var v1 = "123";
var v2 = true;
var v3 = 10;
var v4 = Symbol("foo")

var obj = Object.assign({}, v1, null, v2, undefined, v3, v4); 
// 源對(duì)象如果是原始值,會(huì)被自動(dòng)轉(zhuǎn)換成它們的包裝對(duì)象,
// 而 null 和 undefined 這兩種原始值會(huì)被完全忽略。
// 注意,只有字符串的包裝對(duì)象才有可能有自身可枚舉屬性。
console.log(obj); // { "0": "1", "1": "2", "2": "3" }

例子:拷貝屬性過(guò)程中發(fā)生異常

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

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

console.log(target.bar);  // 2,說(shuō)明第一個(gè)源對(duì)象拷貝成功了。
console.log(target.foo2); // 3,說(shuō)明第二個(gè)源對(duì)象的第一個(gè)屬性也拷貝成功了。
console.log(target.foo);  // 1,只讀屬性不能被覆蓋,所以第二個(gè)源對(duì)象的第二個(gè)屬性拷貝失敗了。
console.log(target.foo3); // undefined,異常之后 assign 方法就退出了,第三個(gè)屬性是不會(huì)被拷貝到的。
console.log(target.baz);  // undefined,第三個(gè)源對(duì)象更是不會(huì)被拷貝到的。
使用 [].concat 來(lái)復(fù)制數(shù)組

同樣類(lèi)似于對(duì)于對(duì)象的復(fù)制,我們建議使用[].concat來(lái)進(jìn)行數(shù)組的深復(fù)制:

var list = [1, 2, 3];
var changedList = [].concat(list);
changedList[1] = 2;
list === changedList; // false

同樣的,concat方法也只能保證一層深復(fù)制:

> list = [[1,2,3]]
[ [ 1, 2, 3 ] ]
> new_list = [].concat(list)
[ [ 1, 2, 3 ] ]
> new_list[0][0] = 4
4
> list
[ [ 4, 2, 3 ] ]
淺拷貝的缺陷

不過(guò)需要注意的是,assign是淺拷貝,或者說(shuō),它是一級(jí)深拷貝,舉兩個(gè)例子說(shuō)明:

const defaultOpt = {
    title: {
        text: "hello world",
        subtext: "It"s my world."
    }
};

const opt = Object.assign({}, defaultOpt, {
    title: {
        subtext: "Yes, your world."
    }
});

console.log(opt);

// 預(yù)期結(jié)果
{
    title: {
        text: "hello world",
        subtext: "Yes, your world."
    }
}
// 實(shí)際結(jié)果
{
    title: {
        subtext: "Yes, your world."
    }
}

上面這個(gè)例子中,對(duì)于對(duì)象的一級(jí)子元素而言,只會(huì)替換引用,而不會(huì)動(dòng)態(tài)的添加內(nèi)容。那么,其實(shí)assign并沒(méi)有解決對(duì)象的引用混亂問(wèn)題,參考下下面這個(gè)例子:

const defaultOpt = {
    title: {
        text: "hello world",
        subtext: "It"s my world."
    } 
};

const opt1 = Object.assign({}, defaultOpt);
const opt2 = Object.assign({}, defaultOpt);
opt2.title.subtext = "Yes, your world.";

console.log("opt1:");
console.log(opt1);
console.log("opt2:");
console.log(opt2);

// 結(jié)果
opt1:
{
    title: {
        text: "hello world",
        subtext: "Yes, your world."
    }
}
opt2:
{
    title: {
        text: "hello world",
        subtext: "Yes, your world."
    }
}
DeepCopy: 深拷貝 遞歸屬性遍歷

一般來(lái)說(shuō),在JavaScript中考慮復(fù)合類(lèi)型的深層復(fù)制的時(shí)候,往往就是指對(duì)于Date、Object與Array這三個(gè)復(fù)合類(lèi)型的處理。我們能想到的最常用的方法就是先創(chuàng)建一個(gè)空的新對(duì)象,然后遞歸遍歷舊對(duì)象,直到發(fā)現(xiàn)基礎(chǔ)類(lèi)型的子節(jié)點(diǎn)才賦予到新對(duì)象對(duì)應(yīng)的位置。不過(guò)這種方法會(huì)存在一個(gè)問(wèn)題,就是JavaScript中存在著神奇的原型機(jī)制,并且這個(gè)原型會(huì)在遍歷的時(shí)候出現(xiàn),然后原型不應(yīng)該被賦予給新對(duì)象。那么在遍歷的過(guò)程中,我們應(yīng)該考慮使用hasOenProperty方法來(lái)過(guò)濾掉那些繼承自原型鏈上的屬性:

function clone(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn"t supported.");
}

調(diào)用如下:

// This would be cloneable:
var tree = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "right" : null,
    "data"  : 8
};

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cylicGraph["right"] = cylicGraph;
利用 JSON 深拷貝
JSON.parse(JSON.stringify(obj));

對(duì)于一般的需求是可以滿足的,但是它有缺點(diǎn)。下例中,可以看到JSON復(fù)制會(huì)忽略掉值為undefined以及函數(shù)表達(dá)式。

var obj = {
    a: 1,
    b: 2,
    c: undefined,
    sum: function() { return a + b; }
};

var obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2);
//Object {a: 1, b: 2}
延伸閱讀

基于 JSX 的動(dòng)態(tài)數(shù)據(jù)綁定

ECMAScript 2017(ES8)特性概述

WebAssembly 初體驗(yàn):從零開(kāi)始重構(gòu)計(jì)算模塊

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/87285.html

相關(guān)文章

  • js拷貝拷貝方法

    摘要:淺拷貝實(shí)現(xiàn)方式賦值。但是進(jìn)行的是淺拷貝,拷貝的是對(duì)象的屬性的引用,而不是對(duì)象本身。沒(méi)復(fù)制遞歸拷貝避免相互引用對(duì)象導(dǎo)致死循環(huán),如的情況使用方法直接使用,可以達(dá)到深拷貝的效果。 js有五種基本數(shù)據(jù)類(lèi)型,string,number,boolean,null,undefind。這五種類(lèi)型的賦值,就是值傳遞。特殊類(lèi)型對(duì)象的賦值是將對(duì)象地址的引用賦值。這時(shí)候修改對(duì)象中的屬性或者值,會(huì)導(dǎo)致所有引用這...

    lewif 評(píng)論0 收藏0
  • 2017-08-15 前端日?qǐng)?bào)

    摘要:前端日?qǐng)?bào)精選變量聲明與賦值值傳遞淺拷貝與深拷貝詳解淺談自適應(yīng)學(xué)習(xí)比你想象的要簡(jiǎn)單常見(jiàn)排序算法之實(shí)現(xiàn)世界萬(wàn)物誕生記中文深入理解筆記與異步編程譯不可變和中的知乎專(zhuān)欄譯怎樣避免開(kāi)發(fā)時(shí)的深坑瘋狂的技術(shù)宅在翻譯網(wǎng)格布局掘金詳解改變模糊度亮 2017-08-15 前端日?qǐng)?bào) 精選 ES6 變量聲明與賦值:值傳遞、淺拷貝與深拷貝詳解淺談web自適應(yīng)學(xué)習(xí) React.js 比你想象的要簡(jiǎn)單常見(jiàn)排序算法之...

    xinhaip 評(píng)論0 收藏0
  • 「前端面試題系列9」拷貝拷貝的含義、區(qū)別及實(shí)現(xiàn)(文末有崗位內(nèi)推哦~)

    摘要:深拷貝與淺拷貝的出現(xiàn),就與這兩個(gè)數(shù)據(jù)類(lèi)型有關(guān)。這時(shí),就需要用淺拷貝來(lái)實(shí)現(xiàn)了。數(shù)據(jù)一但過(guò)多,就會(huì)有遞歸爆棧的風(fēng)險(xiǎn)。這個(gè)方法是在解決遞歸爆棧問(wèn)題的基礎(chǔ)上,加以改進(jìn)解決循環(huán)引用的問(wèn)題。但如果你并不想保持引用,那就改用用于解決遞歸爆棧即可。 前言 這是前端面試題系列的第 9 篇,你可能錯(cuò)過(guò)了前面的篇章,可以在這里找到: 數(shù)組去重(10 種濃縮版) JavaScript 中的事件機(jī)制(從原生到...

    caige 評(píng)論0 收藏0
  • ES6-7

    摘要:的翻譯文檔由的維護(hù)很多人說(shuō),阮老師已經(jīng)有一本關(guān)于的書(shū)了入門(mén),覺(jué)得看看這本書(shū)就足夠了。前端的異步解決方案之和異步編程模式在前端開(kāi)發(fā)過(guò)程中,顯得越來(lái)越重要。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(shū)(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會(huì)promise...... 本書(shū)的目的是以目前還在制定中的ECMASc...

    mudiyouyou 評(píng)論0 收藏0
  • 拷貝拷貝的區(qū)別

    摘要:淺拷貝與深拷貝一數(shù)據(jù)類(lèi)型數(shù)據(jù)分為基本數(shù)據(jù)類(lèi)型,和對(duì)象數(shù)據(jù)類(lèi)型。淺拷貝是按位拷貝對(duì)象,它會(huì)創(chuàng)建一個(gè)新對(duì)象,這個(gè)對(duì)象有著原始對(duì)象屬性值的一份精確拷貝。對(duì)于字符串?dāng)?shù)字及布爾值來(lái)說(shuō)不是或者對(duì)象,會(huì)拷貝這些值到新的數(shù)組里。 淺拷貝與深拷貝 一、數(shù)據(jù)類(lèi)型數(shù)據(jù)分為基本數(shù)據(jù)類(lèi)型(String, Number, Boolean, Null, Undefined,Symbol)和對(duì)象數(shù)據(jù)類(lèi)型。 基本數(shù)據(jù)類(lèi)...

    jsyzchen 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<