ECMAScript6學(xué)習(xí)(二)

函數(shù)的擴(kuò)展

函數(shù)參數(shù)的默認(rèn)值

ES6 允許為函數(shù)的參數(shù)設(shè)置默認(rèn)值,即直接寫在參數(shù)定義的后面。

function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
與解構(gòu)賦值默認(rèn)值結(jié)合使用

請(qǐng)問(wèn)下面兩種寫法有什么差別?

// 寫法一
function m1({x = 0, y = 0} = {}) {
  return [x, y];
}
// 寫法二
function m2({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

上面兩種寫法都對(duì)函數(shù)的參數(shù)設(shè)定了默認(rèn)值,區(qū)別是寫法一函數(shù)參數(shù)的默認(rèn)值是空對(duì)象,但是設(shè)置了對(duì)象解構(gòu)賦值的默認(rèn)值;寫法二函數(shù)參數(shù)的默認(rèn)值是一個(gè)有具體屬性的對(duì)象,但是沒(méi)有設(shè)置對(duì)象解構(gòu)賦值的默認(rèn)值。

// 函數(shù)沒(méi)有參數(shù)的情況
m1() // [0, 0]
m2() // [0, 0]

// x 和 y 都有值的情況
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]

// x 有值,y 無(wú)值的情況
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]

// x 和 y 都無(wú)值的情況
m1({}) // [0, 0];
m2({}) // [undefined, undefined]

m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]
function foo(x = 5, y = 6) {
  console.log(x, y);
}
foo(undefined, null)
// 5 null

上面代碼中,x參數(shù)對(duì)應(yīng)undefined,結(jié)果觸發(fā)了默認(rèn)值,y參數(shù)等于null,就沒(méi)有觸發(fā)默認(rèn)值。

作用域

一旦設(shè)置了參數(shù)的默認(rèn)值,函數(shù)進(jìn)行聲明初始化時(shí),參數(shù)會(huì)形成一個(gè)單獨(dú)的作用域(context)。等到初始化結(jié)束,這個(gè)作用域就會(huì)消失。這種語(yǔ)法行為,在不設(shè)置參數(shù)默認(rèn)值時(shí),是不會(huì)出現(xiàn)的。

var x = 1;

function f(x, y = x) {
  console.log(y);
}
f(2) // 2

上面代碼中,參數(shù)y的默認(rèn)值等于變量x。調(diào)用函數(shù)f時(shí),參數(shù)形成一個(gè)單獨(dú)的作用域。在這個(gè)作用域里面,默認(rèn)值變量x指向第一個(gè)參數(shù)x,而不是全局變量x,所以輸出是2。
再看下面的例子。

let x = 1;

function f(y = x) {
  let x = 2;
  console.log(y);
}

f() // 1

上面代碼中,函數(shù)f調(diào)用時(shí),參數(shù)y = x形成一個(gè)單獨(dú)的作用域。這個(gè)作用域里面,變量x本身沒(méi)有定義,所以指向外層的全局變量x。函數(shù)調(diào)用時(shí),函數(shù)體內(nèi)部的局部變量x影響不到默認(rèn)值變量x。

如果此時(shí),全局變量x不存在,就會(huì)報(bào)錯(cuò)。

function f(y = x) {
  let x = 2;
  console.log(y);
}

f() // ReferenceError: x is not defined

如果參數(shù)的默認(rèn)值是一個(gè)函數(shù),該函數(shù)的作用域也遵守這個(gè)規(guī)則。請(qǐng)看下面的例子。

let foo = 'outer';

function bar(func = () => foo) {
  let foo = 'inner';
  console.log(func());
}

bar(); // outer
上面代碼中,函數(shù)bar的參數(shù)func的默認(rèn)值是一個(gè)匿名函數(shù),返回值為變量foo。函數(shù)參數(shù)形成的單獨(dú)作用域里面,并沒(méi)有定義變量foo,所以foo指向外層的全局變量foo,因此輸出outer。

rest 參數(shù)

ES6 引入 rest 參數(shù)(形式為 ...變量名 ),用于獲取函數(shù)的多余參數(shù),這樣就不需要使用arguments對(duì)象了。rest 參數(shù)搭配的變量是一個(gè)數(shù)組,該變量將多余的參數(shù)放入數(shù)組中。

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

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

  return sum;
}

add(2, 5, 3) // 10

下面是一個(gè) rest 參數(shù)代替arguments變量的例子。

// arguments變量的寫法
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}
// rest參數(shù)的寫法
const sortNumbers = (...numbers) => numbers.sort();

arguments對(duì)象不是數(shù)組,而是一個(gè)類似數(shù)組的對(duì)象。所以為了使用數(shù)組的方法,必須使用Array.prototype.slice.call先將其轉(zhuǎn)為數(shù)組。rest 參數(shù)就不存在這個(gè)問(wèn)題,它就是一個(gè)真正的數(shù)組,數(shù)組特有的方法都可以使用。

注意,rest 參數(shù)之后不能再有其他參數(shù)(即只能是最后一個(gè)參數(shù)),否則會(huì)報(bào)錯(cuò)。

// 報(bào)錯(cuò)
function f(a, ...b, c) {
  // ...
}

下面是 rest 參數(shù)與箭頭函數(shù)結(jié)合的例子。
const numbers = (...nums) => nums;

numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]

const headAndTail = (head, ...tail) => [head, tail];

headAndTail(1, 2, 3, 4, 5)
// [1,[2,3,4,5]]

尾調(diào)用優(yōu)化

我們知道,函數(shù)調(diào)用會(huì)在內(nèi)存形成一個(gè)“調(diào)用記錄”,又稱“調(diào)用幀”(call frame),保存調(diào)用位置和內(nèi)部變量等信息。如果在函數(shù)A的內(nèi)部調(diào)用函數(shù)B,那么在A的調(diào)用幀上方,還會(huì)形成一個(gè)B的調(diào)用幀。等到B運(yùn)行結(jié)束,將結(jié)果返回到A,B的調(diào)用幀才會(huì)消失。如果函數(shù)B內(nèi)部還調(diào)用函數(shù)C,那就還有一個(gè)C的調(diào)用幀,以此類推。所有的調(diào)用幀,就形成一個(gè)“調(diào)用棧”(call stack)。
尾調(diào)用由于是函數(shù)的最后一步操作,所以不需要保留外層函數(shù)的調(diào)用幀,因?yàn)檎{(diào)用位置、內(nèi)部變量等信息都不會(huì)再用到了,只要直接用內(nèi)層函數(shù)的調(diào)用幀,取代外層函數(shù)的調(diào)用幀就可以了。
function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同于

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

// 等同于
g(3);

上面代碼中,如果函數(shù)g不是尾調(diào)用,函數(shù)f就需要保存內(nèi)部變量m和n的值、g的調(diào)用位置等信息。但由于調(diào)用g之后,函數(shù)f就結(jié)束了,所以執(zhí)行到最后一步,完全可以刪除f(x)的調(diào)用幀,只保留g(3)的調(diào)用幀。
這就叫做“尾調(diào)用優(yōu)化”(Tail call optimization),即只保留內(nèi)層函數(shù)的調(diào)用幀。如果所有函數(shù)都是尾調(diào)用,那么完全可以做到每次執(zhí)行時(shí),調(diào)用幀只有一項(xiàng),這將大大節(jié)省內(nèi)存。這就是“尾調(diào)用優(yōu)化”的意義。
注意,只有不再用到外層函數(shù)的內(nèi)部變量,內(nèi)層函數(shù)的調(diào)用幀才會(huì)取代外層函數(shù)的調(diào)用幀,否則就無(wú)法進(jìn)行“尾調(diào)用優(yōu)化”。
function addOne(a){
  var one = 1;
  function inner(b){
    return b + one;
  }
  return inner(a);
}

上面的函數(shù)不會(huì)進(jìn)行尾調(diào)用優(yōu)化,因?yàn)閮?nèi)層函數(shù)inner用到了外層函數(shù)addOne的內(nèi)部變量one。

注意,目前只有 Safari 瀏覽器支持尾調(diào)用優(yōu)化,Chrome 和 Firefox 都不支持。

尾遞歸

函數(shù)調(diào)用自身,稱為遞歸。如果尾調(diào)用自身,就稱為尾遞歸。
遞歸非常耗費(fèi)內(nèi)存,因?yàn)樾枰瑫r(shí)保存成千上百個(gè)調(diào)用幀,很容易發(fā)生“棧溢出”錯(cuò)誤(stack overflow)。但對(duì)于尾遞歸來(lái)說(shuō),由于只存在一個(gè)調(diào)用幀,所以永遠(yuǎn)不會(huì)發(fā)生“棧溢出”錯(cuò)誤。

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120
上面代碼是一個(gè)階乘函數(shù),計(jì)算n的階乘,最多需要保存n個(gè)調(diào)用記錄,復(fù)雜度 O(n) 。

如果改寫成尾遞歸,只保留一個(gè)調(diào)用記錄,復(fù)雜度 O(1) 。

function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5, 1) // 120

數(shù)組的擴(kuò)展

擴(kuò)展運(yùn)算符

擴(kuò)展運(yùn)算符(spread)是三個(gè)點(diǎn)(...)。它好比 rest 參數(shù)的逆運(yùn)算,將一個(gè)數(shù)組轉(zhuǎn)為用逗號(hào)分隔的參數(shù)序列。

console.log(...[1, 2, 3])
// 1 2 3

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

function f(x, y, z) {
    console.log(x,y,z);
}
let args = [0, 1, 2];
f(...args);

通過(guò)push函數(shù),將一個(gè)數(shù)組添加到另一個(gè)數(shù)組的尾部
// ES6 的寫法

let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);
擴(kuò)展運(yùn)算符的應(yīng)用

(1)復(fù)制數(shù)組
數(shù)組是復(fù)合的數(shù)據(jù)類型,直接復(fù)制的話,只是復(fù)制了指向底層數(shù)據(jù)結(jié)構(gòu)的指針,而不是克隆一個(gè)全新的數(shù)組。

const a1 = [1, 2];
const a2 = a1;

a2[0] = 2;
a1 // [2, 2]

上面代碼中,a2并不是a1的克隆,而是指向同一份數(shù)據(jù)的另一個(gè)指針。修改a2,會(huì)直接導(dǎo)致a1的變化。
ES5 只能用變通方法來(lái)復(fù)制數(shù)組。

const a1 = [1, 2];
const a2 = a1.concat();

a2[0] = 2;
a1 // [1, 2]

上面代碼中,a1會(huì)返回原數(shù)組的克隆,再修改a2就不會(huì)對(duì)a1產(chǎn)生影響。

擴(kuò)展運(yùn)算符提供了復(fù)制數(shù)組的簡(jiǎn)便寫法。

const a1 = [1, 2];
// 寫法一
const a2 = [...a1];
// 寫法二
const [...a2] = a1;

上面的兩種寫法,a2都是a1的克隆。

(2)合并數(shù)組
擴(kuò)展運(yùn)算符提供了數(shù)組合并的新寫法。

const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合并數(shù)組
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并數(shù)組
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

(3)字符串
擴(kuò)展運(yùn)算符還可以將字符串轉(zhuǎn)為真正的數(shù)組。

[...'hello']
// [ "h", "e", "l", "l", "o" ]

Array.from()

Array.from方法用于將兩類對(duì)象轉(zhuǎn)為真正的數(shù)組:類似數(shù)組的對(duì)象(array-like object)和可遍歷(iterable)的對(duì)象(包括 ES6 新增的數(shù)據(jù)結(jié)構(gòu) Set 和 Map)。
下面是一個(gè)類似數(shù)組的對(duì)象,Array.from將它轉(zhuǎn)為真正的數(shù)組。

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};

// ES5的寫法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的寫法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

只要是部署了 Iterator 接口的數(shù)據(jù)結(jié)構(gòu),Array.from都能將其轉(zhuǎn)為數(shù)組。

Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']

數(shù)組實(shí)例的 includes()

Array.prototype.includes方法返回一個(gè)布爾值,表示某個(gè)數(shù)組是否包含給定的值,與字符串的includes方法類似。

[1, 2, 3].includes(2)     // true
[1, 2, NaN].includes(NaN) // true

該方法的第二個(gè)參數(shù)表示搜索的起始位置,默認(rèn)為0。如果第二個(gè)參數(shù)為負(fù)數(shù),則表示倒數(shù)的位置,如果這時(shí)它大于數(shù)組長(zhǎng)度(比如第二個(gè)參數(shù)為-4,但數(shù)組長(zhǎng)度為3),則會(huì)重置為從0開(kāi)始。

[1, 2, 3].includes(3, 5);  // false
[1, 2, 3].includes(3, -1); // true

對(duì)象的擴(kuò)展

屬性的遍歷

ES6 一共有 5 種方法可以遍歷對(duì)象的屬性。
(1)for...in
for...in循環(huán)遍歷對(duì)象自身的和繼承的可枚舉屬性(不含 Symbol 屬性)。
(2)Object.keys(obj)
Object.keys返回一個(gè)數(shù)組,包括對(duì)象自身的(不含繼承的)所有可枚舉屬性(不含 Symbol 屬性)的鍵名。
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一個(gè)數(shù)組,包含對(duì)象自身的所有屬性(不含 Symbol 屬性,但是包括不可枚舉屬性)的鍵名。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols返回一個(gè)數(shù)組,包含對(duì)象自身的所有 Symbol 屬性的鍵名。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys返回一個(gè)數(shù)組,包含對(duì)象自身的所有鍵名,不管鍵名是 Symbol 或字符串,也不管是否可枚舉。

super 關(guān)鍵字

我們知道,this關(guān)鍵字總是指向函數(shù)所在的當(dāng)前對(duì)象,ES6 又新增了另一個(gè)類似的關(guān)鍵字super,指向當(dāng)前對(duì)象的原型對(duì)象。

const proto = {
  foo: 'hello'
};

const obj = {
  foo: 'world',
  find() {
    return super.foo;
  }
};

Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

注意,super關(guān)鍵字表示原型對(duì)象時(shí),只能用在對(duì)象的方法之中,用在其他地方都會(huì)報(bào)錯(cuò)。

// 報(bào)錯(cuò)
const obj = {
  foo: super.foo
}

// 報(bào)錯(cuò)
const obj = {
  foo: () => super.foo
}

// 報(bào)錯(cuò)
const obj = {
  foo: function () {
    return super.foo
  }
}
const proto = {
  x: 'hello',
  foo() {
    console.log(this.x);
  },
};

const obj = {
  x: 'world',
  foo() {
    super.foo();
  }
}

Object.setPrototypeOf(obj, proto);

obj.foo() // "world"

上面代碼中,super.foo指向原型對(duì)象proto的foo方法,但是綁定的this卻還是當(dāng)前對(duì)象obj,因此輸出的就是world。

對(duì)象的新增方法

Object.is()

ES5 比較兩個(gè)值是否相等,只有兩個(gè)運(yùn)算符:相等運(yùn)算符(==)和嚴(yán)格相等運(yùn)算符(===)。它們都有缺點(diǎn),前者會(huì)自動(dòng)轉(zhuǎn)換數(shù)據(jù)類型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一種運(yùn)算,在所有環(huán)境中,只要兩個(gè)值是一樣的,它們就應(yīng)該相等。
ES6 提出“Same-value equality”(同值相等)算法,用來(lái)解決這個(gè)問(wèn)題。Object.is就是部署這個(gè)算法的新方法。它用來(lái)比較兩個(gè)值是否嚴(yán)格相等,與嚴(yán)格比較運(yùn)算符(===)的行為基本一致。

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

不同之處只有兩個(gè):一是+0不等于-0,二是NaN等于自身。

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

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

Object.assign()

Object.assign方法用于對(duì)象的合并,將源對(duì)象(source)的所有可枚舉屬性,復(fù)制到目標(biāo)對(duì)象(target)。

const target = { a: 1 };

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

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

注意,如果目標(biāo)對(duì)象與源對(duì)象有同名屬性,或多個(gè)源對(duì)象有同名屬性,則后面的屬性會(huì)覆蓋前面的屬性。

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

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

var res = Object.assign(target, source1, source2);
console.log(res, target, res === target)
res // {a:1, b:2, c:3}
target // {a:1, b:2, c:3}
true

Object.assign拷貝的屬性是有限制的,只拷貝源對(duì)象的自身屬性(不拷貝繼承屬性),也不拷貝不可枚舉的屬性(enumerable: false)。

Object.assign({b: 'c'},
  Object.defineProperty({}, 'invisible', {
    enumerable: false,
    value: 'hello'
  })
)
// { b: 'c' }
注意點(diǎn)

(1)淺拷貝
Object.assign方法實(shí)行的是淺拷貝,而不是深拷貝。也就是說(shuō),如果<<<源對(duì)象某個(gè)屬性的值是對(duì)象>>>,那么目標(biāo)對(duì)象拷貝得到的是這個(gè)對(duì)象的引用。

const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2

如果原對(duì)象的屬性值不是對(duì)象,那么原對(duì)象的改變不會(huì)影響新拷貝的值。

const obj3 = {a: 1};
const obj4 = Object.assign({}, obj3);

obj3.a = 2;
obj4 //{a: 1}

(2)數(shù)組的處理
Object.assign可以用來(lái)處理數(shù)組,但是會(huì)把數(shù)組視為對(duì)象。

Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
常見(jiàn)用途

Object.assign方法有很多用處。
(1)為對(duì)象添加屬性

class Point {
    constructor(x, y) {
        Object.assign(this, { x, y });
    }
}
var p = new Point(1, 2);
console.log(p); //Point {x: 1, y: 2}

上面方法通過(guò)Object.assign方法,將x屬性和y屬性添加到Point類的對(duì)象實(shí)例。

(2)為對(duì)象添加方法

Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});

// 等同于下面的寫法
SomeClass.prototype.someMethod = function (arg1, arg2) {
  ···
};
SomeClass.prototype.anotherMethod = function () {
  ···
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,674評(píng)論 1 32
  • 三,字符串?dāng)U展 3.1 Unicode表示法 ES6 做出了改進(jìn),只要將碼點(diǎn)放入大括號(hào),就能正確解讀該字符。有了這...
    eastbaby閱讀 1,672評(píng)論 0 8
  • 概要 64學(xué)時(shí) 3.5學(xué)分 章節(jié)安排 電子商務(wù)網(wǎng)站概況 HTML5+CSS3 JavaScript Node 電子...
    阿啊阿吖丁閱讀 9,874評(píng)論 0 3
  • 本文為阮一峰大神的《ECMAScript 6 入門》的個(gè)人版提純! babel babel負(fù)責(zé)將JS高級(jí)語(yǔ)法轉(zhuǎn)義,...
    Devildi已被占用閱讀 2,134評(píng)論 0 4
  • 唯一能做的,就是在深夜原諒一切過(guò)往的人和事順?biāo)炱桨?憂郁,是一種力量它可以給予你撥開(kāi)淺薄立于生活邊緣觸摸和感知靈魂...
    嘉溫閱讀 1,154評(píng)論 10 31

友情鏈接更多精彩內(nèi)容