this 問(wèn)題總結(jié)
默認(rèn)綁定(函數(shù)直接調(diào)用)
非嚴(yán)格模式下:
function fn() {
console.log(this) // 打印window 對(duì)象
}
fn()
嚴(yán)格模式下:
function fn() {
'use strict'
console.log(this) // 打印undefined
}
fn()
TIP1 ?? 非嚴(yán)格模式下,默認(rèn)綁定指向全局(
node中式global)
var a = 1
function fn(){
var a = 2
console.log(this.a) // 1
}
fn()
var b = 1
function outer () {
var b = 2
function inner () {
console.log(this.b) // 1
}
inner()
}
outer()
const obj = {
a: 1,
fn: function() {
console.log(this.a)
}
}
obj.fn() // 1
const f = obj.fn
f() // undefined
隱式綁定(屬性訪問(wèn)調(diào)用)
function fn () {
console.log(this.a)
}
const obj = {
a: 1
}
obj.fn = fn
obj.fn() // 1
TIP ?? 隱式綁定的
this指的是調(diào)用堆棧的上一級(jí)(.前面一個(gè))
function fn () {
console.log(this.a)
}
const obj1 = {
a: 1,
fn
}
const obj2 = {
a: 2,
obj1
}
obj2.obj1.fn() // 1
面試官一般問(wèn)的是一些邊界 case,比如隱式綁定失效(列舉部分):
// 第一種 是前面提過(guò)的情況
const obj1 = {
a: 1,
fn: function() {
console.log(this.a)
}
}
const fn1 = obj1.fn // 將引用給了 fn1,等同于寫了 function fn1() { console.log(this.a) }
fn1() // 所以這里其實(shí)已經(jīng)變成了默認(rèn)綁定規(guī)則了,該函數(shù) `fn1` 執(zhí)行的環(huán)境就是全局環(huán)境
// 第二種 setTimeout
setTimeout(obj1.fn, 1000) // 這里執(zhí)行的環(huán)境同樣是全局
// 第三種 函數(shù)作為參數(shù)傳遞
function run(fn) {
fn()
}
run(obj1.fn) // 這里傳進(jìn)去的是一個(gè)引用
// 第四種 一般匿名函數(shù)也是會(huì)指向全局的
var name = 'The Window';
var obj = {
name: 'My obj',
getName: function() {
return function() { // 這是一個(gè)匿名函數(shù)
console.log(this.name)
};
}
}
obj.getName()()
// 第五種 函數(shù)賦值也會(huì)改變 this 指向,下邊練習(xí)題會(huì)有 case,react 中事件處理函數(shù)為啥要 bind 一下的原因
// 第六種 IIFE
顯式綁定(call、 bind、 apply)
通過(guò)顯式的一些方法去強(qiáng)行的綁定 this 上下文
function fn () {
console.log(this.a)
}
const obj = {
a: 100
}
fn.call(obj) // 100
TIP ?? 這種根本還是取決于第一個(gè)參數(shù)
但是第一個(gè)為null的時(shí)候還是綁到全局的
function fn(){
console.log(this)
}
// boxing(裝箱) -> (1 ----> Number(1))
// bind 只看第一個(gè) bind(堆棧的上下文,上一個(gè),寫的順序來(lái)看就是第一個(gè))
fn.bind(1).bind(2)() // Number {1}
js bind 的實(shí)現(xiàn)(FROM MDN):
// Yes, it does work with `new (funcA.bind(thisArg, args))`
if (!Function.prototype.bind) (function(){
var ArrayPrototypeSlice = Array.prototype.slice; // 為了 this
Function.prototype.bind = function(otherThis) {
// 調(diào)用者必須是函數(shù),這里的 this 指向調(diào)用者:fn.bind(ctx, ...args) / fn
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var baseArgs= ArrayPrototypeSlice.call(arguments, 1), // 取余下的參數(shù)
baseArgsLength = baseArgs.length,
fToBind = this, // 調(diào)用者
fNOP = function() {}, // 寄生組合集成需要一個(gè)中間函數(shù),避免兩次構(gòu)造
fBound = function() {
// const newFn = fn.bind(ctx, 1); newFn(2) -> arguments: [1, 2]
baseArgs.length = baseArgsLength; // reset to default base arguments
baseArgs.push.apply(baseArgs, arguments); // 參數(shù)收集
return fToBind.apply( // apply 顯示綁定 this
// 判斷是不是 new 調(diào)用的情況,這里也說(shuō)明了后邊要講的優(yōu)先級(jí)問(wèn)題
fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs
);
};
// 下邊是為了實(shí)現(xiàn)原型繼承
if (this.prototype) { // 函數(shù)的原型指向其構(gòu)造函數(shù),構(gòu)造函數(shù)的原型指向函數(shù)
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype; // 就是讓中間函數(shù)的構(gòu)造函數(shù)指向調(diào)用者的構(gòu)造
}
fBound.prototype = new fNOP(); // 繼承中間函數(shù),其實(shí)這里也繼承了調(diào)用者了
return fBound; // new fn()
};
})();
TIP ?? 如果函數(shù)
constructor里沒(méi)有返回對(duì)象的話,this指向的是new之后得到的實(shí)例
function foo(a) {
this.a = a
}
const f = new foo(2)
f.a // 2
function bar(a) {
this.a = a
return {
a: 100
}
}
const b = new bar(3)
b.a // 100
箭頭函數(shù)
箭頭函數(shù)這種情況比較特殊,編譯期間確定的上下文,不會(huì)被改變,哪怕你 new,指向的就是上一層的上下文,
TIP ?? 箭頭函數(shù)本身是沒(méi)有
this的,繼承的是外層的
function fn() {
return {
b: () => {
console.log(this)
}
}
}
fn().b() // window 與 fn 的this 指向一致
fn().b.bind(1)() // window 與 fn 的this 指向一致
fn.bind(2)().b.bind(3)() // Number{2} 與 fn 的this 指向一致
function fn() {
return {
b: function() {
console.log(this)
}
}
}
fn().b() // {b: ?} 指向 函數(shù)b自己的this
fn().b.bind(1)() // Number{1}
fn.bind(2)().b.bind(3)() // Number{3}
優(yōu)先級(jí)
這上面的各種方式一定是有先后順序的,同時(shí)作用于一個(gè)函數(shù)的時(shí)候,以哪一個(gè)為準(zhǔn)呢?這取決于優(yōu)先級(jí)
// 隱式 vs 默認(rèn) -> 結(jié)論:隱式 > 默認(rèn)
function fn() {
console.log(this)
}
const obj = {
fn
}
obj.fn() //{fn: ?}
// 顯式 vs 隱式 -> 結(jié)論:顯式 > 隱式
obj.fn.bind(5)() // Number {5}
// new vs 顯式 -> 結(jié)論:new > 顯式
function foo (a) {
this.a = a
}
const obj1 = {}
var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a) // 2
// new
var baz = new bar(3)
console.log( obj1.a ) // 2
console.log( baz.a ) // 3
// 箭頭函數(shù)沒(méi)有 this,比較沒(méi)有意義
// 1.
function foo() {
console.log( this.a ) // 2
}
var a = 2;
(function(){
"use strict" // 迷惑大家的
foo(); // 直接函數(shù)執(zhí)行this 指向window
})();
// 2.
var name="the window"
var object={
name:"My Object",
getName: function(){
return this.name
}
}
object.getName() // "My Object"
(object.getName)() // "My Object"
(object.getName = object.getName)() // "the window"
(object.getName, object.getName)() // "the window"
// 3.
var x = 3
var obj3 = {
x: 1,
getX: function() {
var x = 5
return function() {
return this.x
}(); // ??
}
}
console.log(obj3.getX()) // 3
// 4.
function a(x){
this.x = x
return this
}
var x = a(5) // 替換為 let 再試試
var y = a(6) // 替換為 let 再試試 // 再換回 var,但是去掉 y 的情況,再試試
console.log(x.x) // undefine
console.log(y.x) // 6
// 等價(jià)于
window.x = 5;
window.x = window;
window.x = 6;
window.y = window;
console.log(x.x) // void 0 其實(shí)執(zhí)行的是 Number(6).x
console.log(y.x) // 6
作用域和閉包
存儲(chǔ)空間、執(zhí)行上下文
?? 數(shù)據(jù)是怎么存的?
本質(zhì)是將數(shù)據(jù)映射成
01,然后通過(guò)觸發(fā)器存儲(chǔ)這類信息(電信號(hào))
?? 棧 和 堆 / 靜態(tài)內(nèi)存分配 和 動(dòng)態(tài)內(nèi)存分配
堆棧這里指的是存儲(chǔ)數(shù)據(jù)結(jié)構(gòu),當(dāng)然本身也可以是一種數(shù)據(jù)結(jié)構(gòu)的概念(二叉堆、棧)
靜態(tài)內(nèi)存分配:
- 編譯期知道所需內(nèi)存空間大小。
- 編譯期執(zhí)行
- 申請(qǐng)到??臻g
- FILO(先進(jìn)后出)
動(dòng)態(tài)內(nèi)存分配:
- 編譯期不知道所需內(nèi)存空間大小
- 運(yùn)行期執(zhí)行
- 申請(qǐng)到堆空間
- 沒(méi)有特定的順序
當(dāng)控制器轉(zhuǎn)到一段可執(zhí)行代碼的時(shí)候就會(huì)進(jìn)入到一個(gè)執(zhí)行上下文。執(zhí)行上下文是一個(gè)堆棧結(jié)構(gòu)(先進(jìn)后出), 棧底部永遠(yuǎn)是全局上下文,棧頂是當(dāng)前活動(dòng)的上下文。其余都是在等待的狀態(tài),這也印證了JS中函數(shù)執(zhí)行的原子性
可執(zhí)行代碼與執(zhí)行上下文是相對(duì)的,某些時(shí)刻二者等價(jià)
可執(zhí)行代碼(大致可以這么劃分):
- 全局代碼
- 函數(shù)
- eval
執(zhí)行上下文(簡(jiǎn)稱 EC)中主要分為三部分內(nèi)容:
-
VO/AO變量對(duì)象 - 作用域鏈
-
This
所以這個(gè)流程可以梳理出來(lái):
遇到可執(zhí)行代碼
-
創(chuàng)建一個(gè)執(zhí)行上下文 (可執(zhí)行代碼的生命周期:編譯、運(yùn)行)
2.1 初始化
VO2.2 建立作用域鏈
2.3 確定
This上下文 -
可執(zhí)行代碼執(zhí)行階段
3.1 參數(shù)、變量賦值、提升
3.2 函數(shù)引用
3.3 ...
出棧
?? 作用域鏈
每一個(gè)執(zhí)行上下文都與一個(gè)作用域鏈相關(guān)聯(lián)。作用域鏈?zhǔn)且粋€(gè)對(duì)象組成的鏈表,求值標(biāo)識(shí)符的時(shí)候會(huì)搜索它。當(dāng)控制進(jìn)入執(zhí)行上下文時(shí),就根據(jù)代碼類型創(chuàng)建一個(gè)作用域鏈,并用初始化對(duì)象(
VO/AO)填充。執(zhí)行一個(gè)上下文的時(shí)候,其作用域鏈只會(huì)被with聲明和catch語(yǔ)句所影響
var a = 20;
function foo(){
var b = 100;
alert( a + b );
}
foo();
// 兩個(gè)階段:創(chuàng)建 - 執(zhí)行
// --------------------------- 創(chuàng)建 ------------------------------
// 模擬 VO/AO 對(duì)象
AO(foo) {
b: void 0
}
// [[scope]] 不是作用域鏈,只是函數(shù)的一個(gè)屬性(規(guī)范里的,不是實(shí)際實(shí)現(xiàn))
// 在函數(shù)創(chuàng)建時(shí)被存儲(chǔ),靜態(tài)(不變的),永遠(yuǎn)永遠(yuǎn),直到函數(shù)被銷毀
foo.[[scope]]: [VO(global)]
VO(global) {
a: void 0,
foo: Reference<'foo'>
}
// --------------------------- 調(diào)用 ------------------------------
// 可以這么去理解,近似的用一個(gè) concant 模擬,就是將當(dāng)前的活動(dòng)對(duì)象放作用域鏈最前邊
Scope = [AO|VO].concat([[Scope]])
// ---------------------------- 執(zhí)行時(shí) EC --------------------------------
EC(global) {
VO(global) {
a: void 0,
foo: Reference<'foo'>
},
Scope: [VO(global)],
// this
}
EC(foo) {
AO(foo) { // 聲明的變量,參數(shù)
b: void 0
},
Scope: [AO(foo), VO(global)] // 查找順序 -> RHS LHS
}
特殊情況:
-
Function構(gòu)造的函數(shù)[[scope]]里只有全局的變量對(duì)象
// 證明
var a = 10;
function foo(){
var b = 20;
// 函數(shù)聲明
function f1(){ // EC(f1) { Scope: [AO(f1), VO(foo), VO(g)] }
console.log(a, b);
}
// 函數(shù)表達(dá)式
var f2 = function(){
console.log(a, b);
}
var f3 = Function('console.log(a,b)')
f1(); // 10, 20
f2(); // 10, 20
f3(); // 10, b is not defined
}
foo();
-
with&catch&eval
本質(zhì)上
eval之類的恐怖之處是可以很方便的修改作用域鏈,執(zhí)行完后又回歸最初狀態(tài)
// 這樣好理解
Scope = [ withObj|catchObj ].concat( [ AO|VO ].concat( [[ scope ]] ) )
// 初始狀態(tài) [VO(foo), VO(global)]
// with 一下:[VO(with)?, VO(foo), VO(global)]
// with 完事兒了,還要恢復(fù) ??
var a = 15, b = 15;
with( { a: 10 } ){
var a = 30, b = 30;
alert(a); // 30
alert(b); // 30
}
alert(a); // ? answer: 15
alert(b); // 30