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

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

ES6 之前帘营,不能直接為函數(shù)的參數(shù)指定默認(rèn)值,只能采用變通的方法

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

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World

上面代碼檢查函數(shù)log的參數(shù)y有沒有賦值颂鸿,如果沒有择吊,則指定默認(rèn)值為World李根。這種寫法的缺點(diǎn)在于,如果參數(shù)y賦值了几睛,但是對應(yīng)的布爾值為false房轿,則該賦值不起作用。就像上面代碼的最后一行所森,參數(shù)y等于空字符冀续,結(jié)果被改為默認(rèn)值。

為了避免這個問題必峰,通常需要先判斷一下參數(shù)y是否被賦值洪唐,如果沒有,再等于默認(rèn)值吼蚁。

if (typeof y === 'undefined') {
  y = 'World';
}

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

可以看到肝匆,ES6 的寫法比 ES5 簡潔許多粒蜈,而且非常自然。下面是另一個例子旗国。

function Point(x = 0, y = 0) {
  this.x = x;
  this.y = y;
}

const p = new Point();
p // { x: 0, y: 0 }

除了簡潔枯怖,ES6 的寫法還有兩個好處:首先,閱讀代碼的人能曾,可以立刻意識到哪些參數(shù)是可以省略的度硝,不用查看函數(shù)體或文檔;其次寿冕,有利于將來的代碼優(yōu)化蕊程,即使未來的版本在對外接口中,徹底拿掉這個參數(shù)驼唱,也不會導(dǎo)致以前的代碼無法運(yùn)行藻茂。

參數(shù)變量是默認(rèn)聲明的,所以不能用let或const再次聲明玫恳。

function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}

上面代碼中辨赐,參數(shù)變量x是默認(rèn)聲明的,在函數(shù)體中京办,不能用let或const再次聲明掀序,否則會報錯。

使用參數(shù)默認(rèn)值時臂港,函數(shù)不能有同名參數(shù)

// 不報錯
function foo(x, x, y) {
  // ...
}

// 報錯
function foo(x, x, y = 1) {
  // ...
}
// SyntaxError: Duplicate parameter name not allowed in this context

另外森枪,一個容易忽略的地方是视搏,參數(shù)默認(rèn)值不是傳值的,而是每次都重新計算默認(rèn)值表達(dá)式的值县袱。也就是說浑娜,參數(shù)默認(rèn)值是惰性求值的

let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

foo() // 100

x = 100;
foo() // 101

上面代碼中,參數(shù)p的默認(rèn)值是x + 1式散。這時筋遭,每次調(diào)用函數(shù)foo,都會重新計算x + 1暴拄,而不是默認(rèn)p等于 100
....................................................................................................................................
與解構(gòu)賦值默認(rèn)值結(jié)合使用

參數(shù)默認(rèn)值可以與解構(gòu)賦值的默認(rèn)值漓滔,結(jié)合起來使用

function foo({x, y = 5}) {
  console.log(x, y);
}

foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined

上面代碼只使用了對象的解構(gòu)賦值默認(rèn)值,沒有使用函數(shù)參數(shù)的默認(rèn)值乖篷。只有當(dāng)函數(shù)foo的參數(shù)是一個對象時响驴,變量x和y才會通過解構(gòu)賦值生成。如果函數(shù)foo調(diào)用時沒提供參數(shù)撕蔼,變量x和y就不會生成豁鲤,從而報錯。通過提供函數(shù)參數(shù)的默認(rèn)值鲸沮,就可以避免這種情況

function foo({x, y = 5} = {}) {
  console.log(x, y);
}

foo() // undefined 5

上面代碼指定琳骡,如果沒有提供參數(shù),函數(shù)foo的參數(shù)默認(rèn)為一個空對象
下面是另一個解構(gòu)賦值默認(rèn)值的例子

function fetch(url, { body = '', method = 'GET', headers = {} }) {
  console.log(method);
}

fetch('http://example.com', {})
// "GET"

fetch('http://example.com')
// 報錯

上面代碼中讼溺,如果函數(shù)fetch的第二個參數(shù)是一個對象楣号,就可以為它的三個屬性設(shè)置默認(rèn)值。這種寫法不能省略第二個參數(shù)怒坯,如果結(jié)合函數(shù)參數(shù)的默認(rèn)值炫狱,就可以省略第二個參數(shù)。這時敬肚,就出現(xiàn)了雙重默認(rèn)值

function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
  console.log(method);
}

fetch('http://example.com')
// "GET"

上面代碼中毕荐,函數(shù)fetch沒有第二個參數(shù)時束析,函數(shù)參數(shù)的默認(rèn)值就會生效艳馒,然后才是解構(gòu)賦值的默認(rèn)值生效,變量method才會取到默認(rèn)值GET

請問下面兩種寫法有什么差別员寇?

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

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

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

// 函數(shù)沒有參數(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 無值的情況
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]

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

m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]

...................................................................................................................................
參數(shù)默認(rèn)值的位置

通常情況下扳缕,定義了默認(rèn)值的參數(shù)慌闭,應(yīng)該是函數(shù)的尾參數(shù)别威。因?yàn)檫@樣比較容易看出來,到底省略了哪些參數(shù)驴剔。如果非尾部的參數(shù)設(shè)置默認(rèn)值省古,實(shí)際上這個參數(shù)是沒法省略的

// 例一
function f(x = 1, y) {
  return [x, y];
}

f() // [1, undefined]
f(2) // [2, undefined])
f(, 1) // 報錯
f(undefined, 1) // [1, 1]

// 例二
function f(x, y = 5, z) {
  return [x, y, z];
}

f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 報錯
f(1, undefined, 2) // [1, 5, 2]

上面代碼中,有默認(rèn)值的參數(shù)都不是尾參數(shù)丧失。這時豺妓,無法只省略該參數(shù),而不省略它后面的參數(shù)布讹,除非顯式輸入undefined

如果傳入undefined琳拭,將觸發(fā)該參數(shù)等于默認(rèn)值,null則沒有這個效果

function foo(x = 5, y = 6) {
  console.log(x, y);
}

foo(undefined, null)
// 5 null

上面代碼中描验,x參數(shù)對應(yīng)undefined白嘁,結(jié)果觸發(fā)了默認(rèn)值,y參數(shù)等于null膘流,就沒有觸發(fā)默認(rèn)值

..................................................................................................................................

函數(shù)的 length 屬性

指定了默認(rèn)值以后权薯,函數(shù)的length屬性,將返回沒有指定默認(rèn)值的參數(shù)個數(shù)睡扬。也就是說盟蚣,指定了默認(rèn)值后,length屬性將失真

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2

上面代碼中卖怜,length屬性的返回值屎开,等于函數(shù)的參數(shù)個數(shù)減去指定了默認(rèn)值的參數(shù)個數(shù)。比如马靠,上面最后一個函數(shù)奄抽,定義了 3 個參數(shù),其中有一個參數(shù)c指定了默認(rèn)值甩鳄,因此length屬性等于3減去1逞度,最后得到2

這是因?yàn)閘ength屬性的含義是,該函數(shù)預(yù)期傳入的參數(shù)個數(shù)妙啃。某個參數(shù)指定默認(rèn)值以后档泽,預(yù)期傳入的參數(shù)個數(shù)就不包括這個參數(shù)了。同理揖赴,后文的 rest 參數(shù)也不會計入length屬性

(function(...args) {}).length // 0

如果設(shè)置了默認(rèn)值的參數(shù)不是尾參數(shù)馆匿,那么length屬性也不再計入后面的參數(shù)了

(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1


作用域

一旦設(shè)置了參數(shù)的默認(rèn)值,函數(shù)進(jìn)行聲明初始化時燥滑,參數(shù)會形成一個單獨(dú)的作用域(context)渐北。等到初始化結(jié)束,這個作用域就會消失铭拧。這種語法行為赃蛛,在不設(shè)置參數(shù)默認(rèn)值時恃锉,是不會出現(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ù)形成一個單獨(dú)的作用域。在這個作用域里面诵闭,默認(rèn)值變量x指向第一個參數(shù)x炼团,而不是全局變量x,所以輸出是2

再看下面的例子

let x = 1;

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

f() // 1

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

如果此時贸宏,全局變量x不存在,就會報錯

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

f() // ReferenceError: x is not defined

下面這樣寫磕洪,也會報錯

var x = 1;

function foo(x = x) {
  // ...
}

foo() // ReferenceError: x is not defined

上面代碼中吭练,參數(shù)x = x形成一個單獨(dú)作用域。實(shí)際執(zhí)行的是let x = x析显,由于暫時性死區(qū)的原因鲫咽,這行代碼會報錯”x 未定義“

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

let foo = 'outer';

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

bar(); // outer

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

如果寫成下面這樣材蛛,就會報錯

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

bar() // ReferenceError: foo is not defined

上面代碼中,匿名函數(shù)里面的foo指向函數(shù)外層尖昏,但是函數(shù)外層并沒有聲明變量foo仰税,所以就報錯了


  1. rest參數(shù)

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

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

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

  return sum;
}

add(2, 5, 3) // 10

上面代碼的add函數(shù)是一個求和函數(shù)迹淌,利用 rest 參數(shù)河绽,可以向該函數(shù)傳入任意數(shù)目的參數(shù)

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

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

arguments對象不是數(shù)組,而是一個類似數(shù)組的對象唉窃。所以為了使用數(shù)組的方法耙饰,必須使用Array.prototype.slice.call先將其轉(zhuǎn)為數(shù)組。rest 參數(shù)就不存在這個問題纹份,它就是一個真正的數(shù)組苟跪,數(shù)組特有的方法都可以使用。下面是一個利用 rest 參數(shù)改寫數(shù)組push方法的例子

function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
    console.log(item);
  });
}

var a = [];
push(a, 1, 2, 3)

注意蔓涧,rest 參數(shù)之后不能再有其他參數(shù)(即只能是最后一個參數(shù))件已,否則會報錯

// 報錯
function f(a, ...b, c) {
  // ...
}

函數(shù)的length屬性,不包括 rest 參數(shù)

(function(a) {}).length  // 1
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1



3.嚴(yán)格模式

從 ES5 開始元暴,函數(shù)內(nèi)部可以設(shè)定為嚴(yán)格模式

function doSomething(a, b) {
  'use strict';
  // code
}

ES2016 做了一點(diǎn)修改篷扩,規(guī)定只要函數(shù)參數(shù)使用了默認(rèn)值、解構(gòu)賦值茉盏、或者擴(kuò)展運(yùn)算符鉴未,那么函數(shù)內(nèi)部就不能顯式設(shè)定為嚴(yán)格模式,否則會報錯

// 報錯
function doSomething(a, b = a) {
  'use strict';
  // code
}

// 報錯
const doSomething = function ({a, b}) {
  'use strict';
  // code
};

// 報錯
const doSomething = (...a) => {
  'use strict';
  // code
};

const obj = {
  // 報錯
  doSomething({a, b}) {
    'use strict';
    // code
  }
};

這樣規(guī)定的原因是鸠姨,函數(shù)內(nèi)部的嚴(yán)格模式铜秆,同時適用于函數(shù)體和函數(shù)參數(shù)。但是讶迁,函數(shù)執(zhí)行的時候羽峰,先執(zhí)行函數(shù)參數(shù),然后再執(zhí)行函數(shù)體添瓷。這樣就有一個不合理的地方梅屉,只有從函數(shù)體之中,才能知道參數(shù)是否應(yīng)該以嚴(yán)格模式執(zhí)行鳞贷,但是參數(shù)卻應(yīng)該先于函數(shù)體執(zhí)行

// 報錯
function doSomething(value = 070) {
  'use strict';
  return value;
}

上面代碼中坯汤,參數(shù)value的默認(rèn)值是八進(jìn)制數(shù)070,但是嚴(yán)格模式下不能用前綴0表示八進(jìn)制搀愧,所以應(yīng)該報錯惰聂。但是實(shí)際上,JavaScript 引擎會先成功執(zhí)行value = 070咱筛,然后進(jìn)入函數(shù)體內(nèi)部搓幌,發(fā)現(xiàn)需要用嚴(yán)格模式執(zhí)行,這時才會報錯迅箩。

雖然可以先解析函數(shù)體代碼溉愁,再執(zhí)行參數(shù)代碼,但是這樣無疑就增加了復(fù)雜性饲趋。因此拐揭,標(biāo)準(zhǔn)索性禁止了這種用法撤蟆,只要參數(shù)使用了默認(rèn)值、解構(gòu)賦值堂污、或者擴(kuò)展運(yùn)算符家肯,就不能顯式指定嚴(yán)格模式。

兩種方法可以規(guī)避這種限制盟猖。第一種是設(shè)定全局性的嚴(yán)格模式讨衣,這是合法的

'use strict';

function doSomething(a, b = a) {
  // code
}

第二種是把函數(shù)包在一個無參數(shù)的立即執(zhí)行函數(shù)里面

const doSomething = (function () {
  'use strict';
  return function(value = 42) {
    return value;
  };
}());



  1. name屬性

函數(shù)的name屬性,返回該函數(shù)的函數(shù)名

function foo() {}
foo.name // "foo"

這個屬性早就被瀏覽器廣泛支持式镐,但是直到 ES6反镇,才將其寫入了標(biāo)準(zhǔn)。

需要注意的是碟案,ES6 對這個屬性的行為做出了一些修改愿险。如果將一個匿名函數(shù)賦值給一個變量,ES5 的name屬性价说,會返回空字符串辆亏,而 ES6 的name屬性會返回實(shí)際的函數(shù)名

var f = function () {};

// ES5
f.name // ""

// ES6
f.name // "f"

上面代碼中,變量f等于一個匿名函數(shù)鳖目,ES5 和 ES6 的name屬性返回的值不一樣

如果將一個具名函數(shù)賦值給一個變量扮叨,則 ES5 和 ES6 的name屬性都返回這個具名函數(shù)原本的名字

const bar = function baz() {};

// ES5
bar.name // "baz"

// ES6
bar.name // "baz"



  1. 箭頭函數(shù)

ES6 允許使用“箭頭”(=>)定義函數(shù)

var f = v => v;

// 等同于
var f = function (v) {
  return v;
};

如果箭頭函數(shù)不需要參數(shù)或需要多個參數(shù),就使用一個圓括號代表參數(shù)部分

var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

如果箭頭函數(shù)的代碼塊部分多于一條語句领迈,就要使用大括號將它們括起來彻磁,并且使用return語句返回

var sum = (num1, num2) => { return num1 + num2; }

由于大括號被解釋為代碼塊,所以如果箭頭函數(shù)直接返回一個對象狸捅,必須在對象外面加上括號衷蜓,否則會報錯

// 報錯
let getTempItem = id => { id: id, name: "Temp" };

// 不報錯
let getTempItem = id => ({ id: id, name: "Temp" });

如果箭頭函數(shù)只有一行語句,且不需要返回值尘喝,可以采用下面的寫法磁浇,就不用寫大括號了

let fn = () => void doesNotReturn();

箭頭函數(shù)可以與變量解構(gòu)結(jié)合使用

const full = ({ first, last }) => first + ' ' + last;

// 等同于
function full(person) {
  return person.first + ' ' + person.last;
}

箭頭函數(shù)使得表達(dá)更加簡潔

const isEven = n => n % 2 == 0;
const square = n => n * n;

上面代碼只用了兩行,就定義了兩個簡單的工具函數(shù)朽褪。如果不用箭頭函數(shù)置吓,可能就要占用多行,而且還不如現(xiàn)在這樣寫醒目缔赠。

箭頭函數(shù)的一個用處是簡化回調(diào)函數(shù)衍锚。

// 正常函數(shù)寫法
[1,2,3].map(function (x) {
  return x * x;
});

// 箭頭函數(shù)寫法
[1,2,3].map(x => x * x);

另一個例子是

// 正常函數(shù)寫法
var result = values.sort(function (a, b) {
  return a - b;
});

// 箭頭函數(shù)寫法
var result = values.sort((a, b) => a - b);

下面是 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ǎn)
箭頭函數(shù)有幾個使用注意點(diǎn)

(1)函數(shù)體內(nèi)的this對象,就是定義時所在的對象嗤堰,而不是使用時所在的對象戴质。

(2)不可以當(dāng)作構(gòu)造函數(shù),也就是說,不可以使用new命令置森,否則會拋出一個錯誤斗埂。

(3)不可以使用arguments對象符糊,該對象在函數(shù)體內(nèi)不存在凫海。如果要用,可以用 rest 參數(shù)代替男娄。

(4)不可以使用yield命令行贪,因此箭頭函數(shù)不能用作 Generator 函數(shù)

上面四點(diǎn)中,第一點(diǎn)尤其值得注意模闲。this對象的指向是可變的建瘫,但是在箭頭函數(shù)中,它是固定的

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 });
// id: 42

上面代碼中尸折,setTimeout的參數(shù)是一個箭頭函數(shù)啰脚,這個箭頭函數(shù)的定義生效是在foo函數(shù)生成時,而它的真正執(zhí)行要等到 100 毫秒后实夹。如果是普通函數(shù)橄浓,執(zhí)行時this應(yīng)該指向全局對象window,這時應(yīng)該輸出21亮航。但是荸实,箭頭函數(shù)導(dǎo)致this總是指向函數(shù)定義生效時所在的對象(本例是{id: 42}),所以輸出的是42缴淋。

箭頭函數(shù)可以讓setTimeout里面的this准给,綁定定義時所在的作用域,而不是指向運(yùn)行時所在的作用域重抖。下面是另一個例子

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭頭函數(shù)
  setInterval(() => this.s1++, 1000);
  // 普通函數(shù)
  setInterval(function () {
    this.s2++;
  }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0

上面代碼中露氮,Timer函數(shù)內(nèi)部設(shè)置了兩個定時器,分別使用了箭頭函數(shù)和普通函數(shù)钟沛。前者的this綁定定義時所在的作用域(即Timer函數(shù))畔规,后者的this指向運(yùn)行時所在的作用域(即全局對象)。所以讹剔,3100 毫秒之后油讯,timer.s1被更新了 3 次穆碎,而timer.s2一次都沒更新摄悯。

箭頭函數(shù)可以讓this指向固定化,這種特性很有利于封裝回調(diào)函數(shù)偷厦。下面是一個例子由捎,DOM 事件的回調(diào)函數(shù)封裝在一個對象里面

var handler = {
  id: '123456',

  init: function() {
    document.addEventListener('click',
      event => this.doSomething(event.type), false);
  },

  doSomething: function(type) {
    console.log('Handling ' + type  + ' for ' + this.id);
  }
};

上面代碼的init方法中兔综,使用了箭頭函數(shù),這導(dǎo)致這個箭頭函數(shù)里面的this,總是指向handler對象软驰。否則涧窒,回調(diào)函數(shù)運(yùn)行時,this.doSomething這一行會報錯锭亏,因?yàn)榇藭rthis指向document對象纠吴。

this指向的固定化,并不是因?yàn)榧^函數(shù)內(nèi)部有綁定this的機(jī)制慧瘤,實(shí)際原因是箭頭函數(shù)根本沒有自己的this戴已,導(dǎo)致內(nèi)部的this就是外層代碼塊的this。正是因?yàn)樗鼪]有this锅减,所以也就不能用作構(gòu)造函數(shù)

所以糖儡,箭頭函數(shù)轉(zhuǎn)成 ES5 的代碼如下

// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

上面代碼中,轉(zhuǎn)換后的 ES5 版本清楚地說明了怔匣,箭頭函數(shù)里面根本沒有自己的this握联,而是引用外層的this

請問下面的代碼之中有幾個this

function foo() {
  return () => {
    return () => {
      return () => {
        console.log('id:', this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1

上面代碼之中,只有一個this每瞒,就是函數(shù)foo的this金闽,所以t1、t2独泞、t3都輸出同樣的結(jié)果呐矾。因?yàn)樗械膬?nèi)層函數(shù)都是箭頭函數(shù),都沒有自己的this懦砂,它們的this其實(shí)都是最外層foo函數(shù)的this

除了this蜒犯,以下三個變量在箭頭函數(shù)之中也是不存在的,指向外層函數(shù)的對應(yīng)變量:arguments荞膘、super罚随、new.target

function foo() {
  setTimeout(() => {
    console.log('args:', arguments);
  }, 100);
}

foo(2, 4, 6, 8)
// args: [2, 4, 6, 8]

上面代碼中,箭頭函數(shù)內(nèi)部的變量arguments羽资,其實(shí)是函數(shù)foo的arguments變量淘菩。

另外,由于箭頭函數(shù)沒有自己的this屠升,所以當(dāng)然也就不能用call()潮改、apply()、bind()這些方法去改變this的指向

(function() {
  return [
    (() => this.x).bind({ x: 'inner' })()
  ];
}).call({ x: 'outer' });
// ['outer']

上面代碼中腹暖,箭頭函數(shù)沒有自己的this汇在,所以bind方法無效,內(nèi)部的this指向外部的this脏答。

長期以來糕殉,JavaScript 語言的this對象一直是一個令人頭痛的問題亩鬼,在對象方法中使用this,必須非常小心阿蝶。箭頭函數(shù)”綁定”this雳锋,很大程度上解決了這個困擾
.................................................................................................................................


嵌套的箭頭函數(shù)

箭頭函數(shù)內(nèi)部,還可以再使用箭頭函數(shù)羡洁。下面是一個 ES5 語法的多重嵌套函數(shù)

function insert(value) {
  return {into: function (array) {
    return {after: function (afterValue) {
      array.splice(array.indexOf(afterValue) + 1, 0, value);
      return array;
    }};
  }};
}

insert(2).into([1, 3]).after(1); //[1, 2, 3]

上面這個函數(shù)玷过,可以使用箭頭函數(shù)改寫

let insert = (value) => ({into: (array) => ({after: (afterValue) => {
  array.splice(array.indexOf(afterValue) + 1, 0, value);
  return array;
}})});

insert(2).into([1, 3]).after(1); //[1, 2, 3]

下面是一個部署管道機(jī)制(pipeline)的例子,即前一個函數(shù)的輸出是后一個函數(shù)的輸入

const pipeline = (...funcs) =>
  val => funcs.reduce((a, b) => b(a), val);

const plus1 = a => a + 1;
const mult2 = a => a * 2;
const addThenMult = pipeline(plus1, mult2);

addThenMult(5)
// 12

如果覺得上面的寫法可讀性比較差焚廊,也可以采用下面的寫法

const plus1 = a => a + 1;
const mult2 = a => a * 2;

mult2(plus1(5))
// 12
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冶匹,一起剝皮案震驚了整個濱河市习劫,隨后出現(xiàn)的幾起案子咆瘟,更是在濱河造成了極大的恐慌,老刑警劉巖诽里,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件袒餐,死亡現(xiàn)場離奇詭異,居然都是意外死亡谤狡,警方通過查閱死者的電腦和手機(jī)灸眼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來墓懂,“玉大人焰宣,你說我怎么就攤上這事〔蹲校” “怎么了匕积?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長榜跌。 經(jīng)常有香客問我闪唆,道長,這世上最難降的妖魔是什么钓葫? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任悄蕾,我火速辦了婚禮,結(jié)果婚禮上础浮,老公的妹妹穿的比我還像新娘帆调。我一直安慰自己,他們只是感情好豆同,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布番刊。 她就那樣靜靜地躺著,像睡著了一般诱告。 火紅的嫁衣襯著肌膚如雪撵枢。 梳的紋絲不亂的頭發(fā)上民晒,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機(jī)與錄音锄禽,去河邊找鬼潜必。 笑死,一個胖子當(dāng)著我的面吹牛沃但,可吹牛的內(nèi)容都是我干的磁滚。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼宵晚,長吁一口氣:“原來是場噩夢啊……” “哼垂攘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起淤刃,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤晒他,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后逸贾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陨仅,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年铝侵,在試婚紗的時候發(fā)現(xiàn)自己被綠了灼伤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡咪鲜,死狀恐怖狐赡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情疟丙,我是刑警寧澤颖侄,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站隆敢,受9級特大地震影響发皿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拂蝎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一穴墅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧温自,春花似錦玄货、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至馆里,卻和暖如春隘世,著一層夾襖步出監(jiān)牢的瞬間可柿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工丙者, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留复斥,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓械媒,卻偏偏與公主長得像目锭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子纷捞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容