1. this指向問題
1.1 認(rèn)識詞法作用域
其實(shí)我們js中的作用域就是詞法作用域,我們會發(fā)現(xiàn)詞法作用域最重要的特征是發(fā)生函數(shù)定義時(shí)確定的,
動態(tài)作用域呢是在函數(shù)運(yùn)行時(shí)確定的形式,而不是函數(shù)定義時(shí)確定的形式
function foo(){
console.log(a);
}
function bar(){
var a = 3;
foo();
}
bar();
var a = 2;
如果js具有動態(tài)作用域那么這里應(yīng)該打印3,但是不好意思告訴你們,js只有詞法作用域,但是js的函數(shù)內(nèi)部有一個(gè)在某種程度上和動態(tài)作用域很像的,那就是this
主要區(qū)別是
詞法作用域是在函數(shù)定義時(shí)確定的
動態(tài)作用域是在函數(shù)運(yùn)行時(shí)確定的,this也是這樣
詞法作用域關(guān)心的是函數(shù)在何處定義,動態(tài)作用域關(guān)注函數(shù)在何處被調(diào)用
既然動態(tài)作用域和this都只關(guān)心在什么位置被調(diào)用,那么我們就不得不提的調(diào)用棧,調(diào)用位置
1.2 理解調(diào)用棧
調(diào)用位置就是函數(shù)被調(diào)用的位置,調(diào)用棧是是為了能夠到達(dá)當(dāng)前執(zhí)行位置所調(diào)用的所有函數(shù)
調(diào)用位置就在當(dāng)前賑災(zāi)執(zhí)行的函數(shù)的前一個(gè)調(diào)用中
function baz(){
// 當(dāng)前調(diào)用棧 是 baz
// 因此調(diào)用位置是在全局
console.log("baz");
bar(); // bar的調(diào)用位置
}
function bar(){
// 當(dāng)前調(diào)用棧 是 baz -- bar
// 因此調(diào)用位置是baz
console.log("bar");
foo(); // foo的調(diào)用位置
}
function foo(){
// 當(dāng)前調(diào)用棧 是 baz -- bar -- foo
// 因此調(diào)用位置是bar中
console.log("foo");
}
baz() // baz的調(diào)用位置
在調(diào)用棧中分析調(diào)用位置,因?yàn)樗麤Q定this的綁定
1.3 this 指向問題
this關(guān)鍵字是JS中最復(fù)雜的機(jī)制,它是一個(gè)很特別的關(guān)鍵字,被自動定義在所有函數(shù)的作用域中.
function foo(){
console.log(this)
}
foo();
this指向的是一個(gè)對象,我們把this指向的對象叫做函數(shù)執(zhí)行的上下文對象
當(dāng)函數(shù)被調(diào)用的時(shí)候,this指向會發(fā)生改變,指向調(diào)用函數(shù)的對象
在函數(shù)預(yù)編譯階段,會生成AO對象,程序還會默認(rèn)把this作為AO對象的一個(gè)屬性名,默認(rèn)屬性值是window
2.this綁定的規(guī)則
2.1 默認(rèn)綁定
就是直接使用不帶任何修飾的函數(shù)引用進(jìn)行的調(diào)用,就是默認(rèn)綁定,無法應(yīng)用其他規(guī)則
function fn(){
console.log(this);
}
fn();
// 改變
function fn(){
console.log(this.a);
}
var a = 2;
fn();
2.2 隱式綁定
考慮函數(shù)調(diào)用位置是否有上下文對象,或者說是否被某個(gè)函數(shù)擁有或包含
示例:
function fn(){
console.log(this);
}
var obj = {
a : 33,
fn:fn
}
obj.fn(); // obj
對象引用鏈只有上一層或者說最后一層在調(diào)用的位置中起作用
function foo(){
console.log(this.a);
}
var obj2 = {
a: 42,
foo: foo
}
var obj1 = {
a: 22,
obj2: obj2
}
obj1.obj2.foo(); // 42
隱式綁定的函數(shù)會丟失綁定對象
function foo(){
console.log(this.a);
}
var obj = {
a : 20,
foo: foo
}
var a = 33;
var bar = obj.foo;
bar(); //33
// 此時(shí)賦值的知識函數(shù)的引用,bar則是一個(gè)不帶任何修飾的函數(shù)調(diào)用,因此應(yīng)用了默認(rèn)綁定
測試
var name = "wuwei";
function showName(){
console.log(this.name);
}
var person1 = {
name: "old",
sayName:showName
}
var person2 = {
name: "xiaoming",
sayName: function(){
var fun = person1.sayName;
fun();
}
}
person1.sayName(); // old
person2.sayName(); // wuwei
2.3 顯示綁定
我們發(fā)現(xiàn)隱式綁定時(shí),必須在一個(gè)對象內(nèi)部包含一個(gè)指向函數(shù)的屬性.并通過這個(gè)屬性間接的應(yīng)用函數(shù).從而把this隱式的綁定到這個(gè)對象上.
如果我們不想在函數(shù)內(nèi)部包含函數(shù)的引用呢.我們就只能用接下來要學(xué)習(xí)的方法,顯示的綁定了
2.3.1 call和applay方法
call && apply方法,
作用.都是在函數(shù)執(zhí)行時(shí),修改this的指向
call 和 apply方法由兩層意思
- 函數(shù)執(zhí)行
- 在函數(shù)執(zhí)行的時(shí)候修改函數(shù)內(nèi)部的this指向
這兩個(gè)方法存在于函數(shù)的原型上,至于是原型 ,咱們以后會學(xué),你只要知道函數(shù)可以調(diào)用這兩個(gè)方法就行了
使用
function foo(){
console.log(this.a)
}
var obj = {
a: 20
}
foo.call(obj);
這里就是通過call這個(gè)方法子foo函數(shù)調(diào)用的時(shí)候?qū)⒑瘮?shù)內(nèi)部的this綁定到了obj上
如果我傳入一個(gè)數(shù)字,字符串 布爾值怎么辦
function foo(){
console.log(this.a)
}
var obj = {
a: 20
}
foo.call(true);
// 為什么是undefined 而不報(bào)錯(cuò)呢
這是包裝類,這里第一參數(shù)必須是對象,如果不是對象,會將其轉(zhuǎn)為包裝對象,轉(zhuǎn)不了就是window
call && apply 方法的區(qū)別
使用方法
fn.call(obj,attr1,attr2,attr3,..);
fn.applay(obj,[attr1,attr2,attr3,..])
這兩個(gè)方法傳參的方式不同,apply只能傳數(shù)組
示例:
function add(c,d){
return this.a + this.b + c + d;
}
var s = {a:1,b:2};
console.log(add.call(s,3,4));
console.log(add.apply(s,[5,6]));
上面程序的輸出結(jié)果是什么?
2.3.2硬綁定
function foo(){
console.log(this.a);
}
var obj = {
a: 35
}
var bar = function(){
foo.call(obj);
}
bar();
bar.call(window);
因?yàn)橛步壎ǚ浅3S玫姆椒?所有ES5也提供了內(nèi)置的方便.bind
function foo(){
console.log(this);
console.log(this.a) ;
}
var obj = {
a: 23
}
var bar = foo.bind(obj)
bar();
此時(shí)就算你使用call,apply 也改變不了函數(shù)的bar的this綁定
function foo(){
console.log(this);
console.log(this.a) ;
}
var obj = {
a: 23
}
var a = 33;
var bar = foo.bind(obj)
bar.call(window);
2.4 new 操作符
new操作符在構(gòu)造函數(shù)那一節(jié)的的時(shí)候已經(jīng)講過,
現(xiàn)在我們就知道關(guān)于this指向的綁定情況有四種.那么接下來看看優(yōu)先級的問題
3. 優(yōu)先級
你需要做的就是找到函數(shù)的調(diào)用位置并判斷應(yīng)用了那條規(guī)則,如果某個(gè)調(diào)用位置可以應(yīng)用多條規(guī)則,我們就需要通過優(yōu)先級來區(qū)分
默認(rèn)的優(yōu)先級最低
-
顯示優(yōu)先級高于隱式
function foo(){ console.log(this.a) } var obj1 = { a: 10, foo: foo } var obj2 = { a: 20, foo: foo } obj1.foo(); // 10 obj2.foo(); // 20 obj1.foo.call(obj2); // 20 obj2.foo.call(obj1); // 10 -
new綁定比隱式綁定優(yōu)先級高
function foo(aa){ this.a = aa } var obj = { foo.foo } obj.foo(2); console.log(obj.a); // 2 var bar = new obj.foo(4); console.log(obj.a); // 2 console.log(bar.a); // 4 -
new操作符比顯示綁定中的硬綁定優(yōu)先級高
function foo(aa){ this.a = aa; } var obj = {} var bar = foo.bind(obj); bar(20); console.log(obj.a); var baz = new bar(30); console.log(obj.a); console.log(baz.a);至于為什么用new操作符還要用bind綁定是為了提前傳一些參數(shù)進(jìn)去
判斷原則
- 函數(shù)是否使用new操作符調(diào)用,如果有,那么this將綁定到新創(chuàng)建的對象
- 函數(shù)是否使用call,apply或者硬綁定,如果有,this執(zhí)行顯示綁定的對象
- 函數(shù)是否在某個(gè)上下文對象中調(diào)用,如果有this指向那個(gè)上下文對象(隱式綁定)
- 如果以上都不是,那就是默認(rèn)綁定,
4. 特殊情況
被忽略的this
function foo(){
console.log(this.a)
}
var a = 2;
foo.call(null); // 2
如果你傳入null或undefined將會忽略傳入的值,實(shí)際使用默認(rèn)值
那么什么時(shí)候會需要用到傳入null的情況呢
使用apply展開參數(shù)
function foo(a,b){
console.log("a: "+ a + " ,b: " + b);
}
foo.apply(null,[2,3]);
因?yàn)檫@里我們不需要關(guān)心this指向,那么我們用null占位最好,
題:
var a = 10;
function aa(){
console.log(a);
a = 0;
console.log(this.a);
var a;
console.log(a);
}
aa();