前端系統(tǒng)學(xué)習(xí) 9. this 指針/閉包/作用域

this 指針詳解

概念

this 是當(dāng)前函數(shù)/當(dāng)前模塊運(yùn)行環(huán)境的上下文铣缠,是一個(gè)指針變量,普通函數(shù)中的this 是在調(diào)用時(shí)才被綁定確認(rèn)指向的鹦付。

通過(guò)不同的 this 調(diào)用同一個(gè)函數(shù)尚粘,可以產(chǎn)出不同的結(jié)果

到底如何確認(rèn) this 綁定的內(nèi)容

this 的綁定規(guī)則

1. 默認(rèn)綁定

直接調(diào)用函數(shù),不使用點(diǎn)操作符調(diào)用

非嚴(yán)格模式下指向全局對(duì)象敲长,瀏覽器環(huán)境是 window郎嫁,node 環(huán)境是 global
嚴(yán)格模式下綁定到 undefined。

Tips: 普通函數(shù)做為參數(shù)傳遞的情況, 比如setTimeout, setInterval, 非嚴(yán)格模式下的this指向全局對(duì)象

2. 隱式綁定

某個(gè)對(duì)象通過(guò)點(diǎn)運(yùn)算符調(diào)用函數(shù)祈噪。this 指向該對(duì)象泽铛。

3. 顯示綁定

通過(guò) bind、call钳降、apply 把函數(shù)綁定到指定的變量上厚宰。

  1. bind 展開(kāi)傳參,只綁定不調(diào)用,返回一個(gè)新函數(shù)
  2. call 展開(kāi)傳參铲觉,綁定的同時(shí)執(zhí)行函數(shù)
  3. apply 傳入?yún)?shù)列表澈蝙,綁定的同時(shí)執(zhí)行函數(shù)
function person (name, sex) {
  console.log(this, name, sex)
}

const obj = {
  name: 'ss',
  sex: 'M'
}

person.call(obj, 'ss', 'M')
person.apply(obj, ['ss', 'M'])

const b = person.bind(obj, 'ss')
b()

Tips:如果我們傳入 call/apply/bind 的第一個(gè)參數(shù)是一個(gè)數(shù)字、布爾值撵幽、string 等基本數(shù)據(jù)類(lèi)型灯荧,綁定時(shí)會(huì)裝箱成一個(gè)對(duì)象

手寫(xiě) bind

Function.prototype.bind2 = function (context) {
  context = context || window

  const fnSymbol = Symbol('fn')
  context[fnSymbol] = this
  let args = Array.prototype.slice.call(arguments, 1)

  return function () {
    args = args.concat(Array.from(arguments))
    
    context[fnSymbol](...args)

    delete context[fnSymbol]
  }
}

function foo (a, b) {
  console.log(this, a, b)
}

const obj = {
  name: 'ss',
  sex: 'M'
}

const f2 = foo.bind2(obj, 'hahah')

f2('ss')

const f3 = foo.bind(obj, 'hahah')
f3('ss')

call

Function.prototype.call2 = function (context, ...args) {
  context = Object(context || window)
  const fnSymbol = Symbol('fn')
  context[fnSymbol] = this
  const result = context[fnSymbol](...args)
  delete context[fnSymbol]
  return result
}

function foo (a, b) {
  console.log(this, a, b)
}

const obj = {
  name: 'ss'
}

foo.call2(obj, 3, 5)
console.log(obj)
foo.call(obj, 3, 5)

4. new 綁定

new 的功能

  1. 創(chuàng)建一個(gè)空對(duì)象
  2. 將對(duì)象的proto 屬性指向構(gòu)造函數(shù)的 prototype
  3. 將構(gòu)造函數(shù)的 this 指向該新建對(duì)象,執(zhí)行構(gòu)造函數(shù)
  4. 如果構(gòu)造函數(shù)執(zhí)行結(jié)果為對(duì)象盐杂,則返回該對(duì)象逗载,否則返回之前新建的對(duì)象

構(gòu)造函數(shù)中的 this 指向了新生成的實(shí)例對(duì)象 studyDay

function study (name) {
  this.name = name
}

const studyDay = new study('ss')
console.log(studyDay) // { name: 'ss' }
console.log(studyDay.name) // ss

5. this 綁定的優(yōu)先級(jí)

new > 顯式 > 隱式 > 默認(rèn)

箭頭函數(shù)

  • 箭頭函數(shù)沒(méi)有 arguments
  • 箭頭函數(shù)不能用作構(gòu)造函數(shù)
  • 箭頭函數(shù)沒(méi)有原型對(duì)象
let fun = () => {}
console.log(fun.prototype) // undefined
  • 箭頭函數(shù)沒(méi)有自己的 this

閉包的概念及應(yīng)用場(chǎng)景

閉包是能訪問(wèn)自由變量的函數(shù)
自由變量是指在函數(shù)中使用的,既不是函數(shù)局部變量也不是函數(shù)參數(shù)的變量

  1. 從理論角度:所有的函數(shù)都是閉包链烈。因?yàn)樗麄兌际窃趧?chuàng)建的時(shí)候就將上層上下文數(shù)據(jù)保存起來(lái)了厉斟。哪怕是簡(jiǎn)單的全局變量也是如此,因?yàn)楹瘮?shù)中訪問(wèn)全局變量就相當(dāng)于在訪問(wèn)自由變量强衡,這個(gè)時(shí)候使用最外層的作用域
  2. 從實(shí)踐角度:以下函數(shù)才算是閉包:
    • 即使創(chuàng)建他的上下文已經(jīng)銷(xiāo)毀擦秽,他仍然存在(比如內(nèi)部函數(shù)從父函數(shù)中返回)
    • 在代碼中引用了自由變量

應(yīng)用場(chǎng)景

  1. 柯里化的目的在于:避免頻繁調(diào)用傳相同參數(shù)的函數(shù)。同時(shí)又能夠輕松復(fù)用漩勤。
    其實(shí)就是封裝一個(gè)高階函數(shù)
function getArea(width, height) {
  return width * height
}

function getWidthArea(width) {
  return function (height) {
    return width * height
  }
}

const getArea2 = getWidthArea(2)
console.log(getArea2(6))

const getArea10 = getWidthArea(10)
console.log(getArea10(11))
  1. 使用閉包實(shí)現(xiàn)私有方法和變量
function Person () {
  let _name = 'ss'

  return {
    getName() {
      return _name
    },
    setName(name) {
      _name = name
    }
  }
}

const person = Person()
person.setName('xy')
console.log(person.getName())
  1. 匿名自執(zhí)行函數(shù)
const funcOne = (function(){
  let i = 0
  return () => {
    i++
    console.log(i)
  }
})()

funcOne() // 1
funcOne() // 2
funcOne() // 3
  1. 緩存一些結(jié)果
    比如在外部函數(shù)創(chuàng)建一個(gè)數(shù)組感挥,閉包函數(shù)內(nèi)可以更改/獲取這個(gè)數(shù)組的值,其實(shí)還是延長(zhǎng)變量的聲明周期越败,但是不通過(guò)全局變量來(lái)實(shí)現(xiàn)触幼。
function funParent () {
  let memo = []
  function funTwo (i) {
    memo.push(i)
    console.log(memo.join(,))
  }
  return funTwo
}

const fn = funParent()

fn(1)
fn(2)

總結(jié)

  • 創(chuàng)建私有變量
  • 延長(zhǎng)變量的生命周期
    一般函數(shù)的詞法環(huán)境在函數(shù)返回后就被銷(xiāo)毀了,但是閉包會(huì)保存對(duì)創(chuàng)建時(shí)所在詞法環(huán)境的引用究飞,即便創(chuàng)建時(shí)所在的執(zhí)行上下文被銷(xiāo)毀置谦,但創(chuàng)建時(shí)所在詞法環(huán)境依然存在,以達(dá)到延長(zhǎng)變量的生命周期的目的

代碼題

  1. 實(shí)現(xiàn) compose 函數(shù)噪猾,得到如下輸出
function fn1(x) {
  return x + 1
}

function fn2(x) {
  return x + 2
}

function fn3(x) {
  return x + 3
}

function fn4(x) {
  return x + 4
}

const a = compose(fn1, fn2, fn3, fn4);
console.log(a('1')); // 1+4+3+2+1=11

function compose (...fns) {
  fns = fns.reverse()
  return (x) => {
    return fns.reduce((pre, cur) => cur(pre), x)
  }
}
  1. 實(shí)現(xiàn)一個(gè)柯里化函數(shù)
function currying(fn, ...args) {
  const fnArgLength = fn.length

  const retFunc = (..._args) => {
    args = args.concat(_args)
    return args.length >= fnArgLength
    ? fn(...args)
    : retFunc
  }

  return retFunc
}

const add = (a, b, c) => a + b + c;
const a1 = currying(add, 1, 2, 3, 4);
const a2 = a1();
console.log(a2) // 6

作用域

作用域是在運(yùn)行時(shí)代碼中的某些特定部分中變量霉祸,函數(shù)和對(duì)象的可訪問(wèn)性。

換句話說(shuō)袱蜡,作用域決定了代碼區(qū)塊中變量和其他資源的可見(jiàn)性

作用域就是一個(gè)獨(dú)立的地盤(pán)丝蹭,讓變量不會(huì)外泄、暴露出去坪蚁。也就是說(shuō)奔穿,作用域最大的用處就是隔離變量,不同作用域下同名變量不會(huì)有沖突敏晤。

ES6 之前 JavaScript 沒(méi)有塊級(jí)作用域贱田,只有全局作用域和函數(shù)作用域。ES6 的到來(lái)嘴脾,為我們提供了塊級(jí)作用域男摧,可通過(guò)letconst來(lái)定義塊級(jí)作用域變量

全局作用域

在代碼中任何地方都可以訪問(wèn)變量和函數(shù)擁有全局作用域

  • 最外層函數(shù) 和 定義在最外層函數(shù)外面的變量 有全局作用域
var outVariable = "我是最外層變量"; //最外層變量
function outFun() { //最外層函數(shù)
    var inVariable = "內(nèi)層變量";
    function innerFun() { //內(nèi)層函數(shù)
        console.log(inVariable);
    }
    innerFun();
}
console.log(outVariable); //我是最外層變量
outFun(); //內(nèi)層變量
console.log(inVariable); //inVariable is not defined
innerFun(); //innerFun is not defined
  • 所有未定義直接賦值的變量自動(dòng)聲明為擁有全局作用域的變量
function outFunc2() {
  variable = '未定義直接復(fù)制的變量'

  var inVariable2 = '內(nèi)層變量2'
}

outFunc()
console.log(variable) // 未定義直接復(fù)制的變量
console.log(inVariable2); //inVariable2 is not defined
  • 所有 window 對(duì)象的屬性都有全局作用域

window.location

  • 弊端

定義全局變量容易造成全局命名空間污染蔬墩,引起變量沖突

函數(shù)作用域

函數(shù)作用域,是指聲明在函數(shù)內(nèi)部的變量耗拓,只有在函數(shù)內(nèi)部才能訪問(wèn)到

作用域是分層的拇颅,內(nèi)層作用域可以訪問(wèn)外層作用域的變量,反之不行

塊級(jí)作用域

塊級(jí)作用域可通過(guò)新增命令 let 和 const 聲明乔询,所聲明的變量在指定塊的作用域外無(wú)法被訪問(wèn)樟插。塊級(jí)作用域 在如下情況下被創(chuàng)建:

  • 在一個(gè)函數(shù)內(nèi)部
  • 在一個(gè)代碼塊(由一對(duì)花括號(hào)包裹)內(nèi)部

let 聲明的語(yǔ)法與 var 的語(yǔ)法一致。你基本上可以用 let 來(lái)代替 var 進(jìn)行變量聲明竿刁,但會(huì)將變量的作用域限制在當(dāng)前代碼塊中黄锤。

塊級(jí)作用域有以下幾個(gè)特點(diǎn):

  • 聲明變量不會(huì)提升到代碼塊頂部
  • 禁止重復(fù)聲明
  • 變量只在當(dāng)前塊內(nèi)有效

作用域鏈

有點(diǎn)兒類(lèi)似于原型鏈,在原型鏈上早一個(gè)屬性的時(shí)候食拜,如果當(dāng)前實(shí)例找不到鸵熟,就回去父級(jí)原型上去找。

作用域鏈也是類(lèi)似的原理负甸,找一個(gè)變量的時(shí)候旅赢,如果當(dāng)前作用域找不到,就會(huì)逐級(jí)網(wǎng)上查找惑惶,找到找到全局作用域還是沒(méi)有找到,就真的找不到了短纵。

Tips: 函數(shù)內(nèi)有效的作用域鏈值指的是創(chuàng)建函數(shù)時(shí)的作用域鏈带污,不是執(zhí)行函數(shù)時(shí)的作用域鏈

const foo = (function() {
  let a = 1
  return function() {
    console.log(a)
  }
})();
(function(){
  let a = 3
  foo()
})()
// 1

const foo = (function() {
  // let a = 1
  return function() {
    console.log(a)
  }
})();
(function(){
  let a = 3
  foo()
})()
// a is not defined

const foo = (function() {
  // let a = 1
  return function() {
    console.log(a)
  }
})();
let a = 2;
(function(){
  let a = 3
  foo()
})()
// 2

Coding

  1. 看一下輸出
var b = 10;
(function b(){
    b = 20;
  // 內(nèi)部作用域,會(huì)先去查找是有已有變量b的聲明香到,有就直接賦值20鱼冀,確實(shí)有了呀。發(fā)現(xiàn)了具名函數(shù) function b(){}悠就,拿此b做賦值千绪;
  // IIFE的函數(shù)無(wú)法進(jìn)行賦值(內(nèi)部機(jī)制,類(lèi)似const定義的常量)梗脾,所以無(wú)效荸型。
    console.log(b); // fn b
  console.log(window.b); // 10
})();
  • IIFE 中的函數(shù)是函數(shù)表達(dá)式不是函數(shù)聲明
  • 函數(shù)表達(dá)式與函數(shù)聲明不同,函數(shù)名只在函數(shù)內(nèi)部有效炸茧,并且此綁定是常量綁定
  • 對(duì)一個(gè)常量賦值瑞妇,在嚴(yán)格模式下報(bào)錯(cuò),非嚴(yán)格模式靜默失敗
  1. 看一下輸出
var a = 3;

function c() {
    alert(a);
}
(function () {
    var a = 4;
    c(); // 3
})();
  1. 看一下輸出
function v() {
  var a = 6

  function a () {

  }

  console.log(a) // 6
}

js會(huì)把所有變量都集中提升到作用域頂部事先聲明好梭冠,但是它賦值的時(shí)機(jī)是依賴(lài)于代碼的位置辕狰,那么js解析運(yùn)行到那一行之后才會(huì)進(jìn)行賦值,還沒(méi)有運(yùn)行到的就不會(huì)事先賦值控漠。也就是變量會(huì)事先聲明蔓倍,但是變量不會(huì)事先賦值。

碰到這種問(wèn)題可以先想一下變量提升和函數(shù)聲明提升的規(guī)則, 原則上是變量被提升到最頂部, 函數(shù)聲明被提升到最頂部變量的下方.

嘗試著把這兩段代碼在大腦中編譯一下:

  • 第一段代碼
function v() {
    var a;
    function a() {

    }
    a=6;
    console.log(a);
}

v(); // 6
  • 第二段代碼
function v() {
    var a;
    function a() {

    }
    console.log(a);
}

v(); // fn a
  1. 看一下輸出
function v() {
    console.log(a); // fn a

    var a = 1;

    console.log(a); // 1

    function a() {

    }

    console.log(a); // 1

    console.log(b); // fn b

    var b = 2;

    console.log(b); // 2

    function b() {

    }

    console.log(b); // 2
}
v();

按照剛才的思路轉(zhuǎn)換一下:

function v() {
    var a;
    var b;
    function a() {}
    function b() {}

    console.log(a); // fn a
    a=1;
    console.log(a); // 1
    console.log(a); // 1

    console.log(b); // fn b
    b=2;
    console.log(b); // 2
    console.log(b); // 2
}
v();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市偶翅,隨后出現(xiàn)的幾起案子默勾,更是在濱河造成了極大的恐慌,老刑警劉巖倒堕,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灾测,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡垦巴,警方通過(guò)查閱死者的電腦和手機(jī)媳搪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)骤宣,“玉大人忆首,你說(shuō)我怎么就攤上這事白华。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵招盲,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我绒尊,道長(zhǎng)篮迎,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任锰霜,我火速辦了婚禮筹误,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘癣缅。我一直安慰自己厨剪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布友存。 她就那樣靜靜地躺著祷膳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪屡立。 梳的紋絲不亂的頭發(fā)上直晨,一...
    開(kāi)封第一講書(shū)人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音侠驯,去河邊找鬼抡秆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛吟策,可吹牛的內(nèi)容都是我干的儒士。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼檩坚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼着撩!你這毒婦竟也來(lái)了诅福?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拖叙,失蹤者是張志新(化名)和其女友劉穎氓润,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體薯鳍,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咖气,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挖滤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片崩溪。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖斩松,靈堂內(nèi)的尸體忽然破棺而出伶唯,到底是詐尸還是另有隱情,我是刑警寧澤惧盹,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布乳幸,位于F島的核電站,受9級(jí)特大地震影響钧椰,放射性物質(zhì)發(fā)生泄漏粹断。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一嫡霞、第九天 我趴在偏房一處隱蔽的房頂上張望姿染。 院中可真熱鬧,春花似錦秒际、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至盾戴,卻和暖如春寄锐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尖啡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工橄仆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衅斩。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓盆顾,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親畏梆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子您宪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355