首頁 > web前端 > js教程 > 主體

深入理解javascript中this指針

小云云
發布: 2018-03-17 16:21:10
原創
1580 人瀏覽過

本文主要跟大家分享深入理解javascript中this指針,在寫java的時候,this用錯了,idea都會直接報錯!

例如…



#在物件導向程式設計裡有兩個重要的概念:一個是類,一個是實例化的物件,類別是一個抽象的概念,用個形象的比喻表述的話,類別就像一個模具,而實例化物件就是透過這個模具製造出來的產品,實例化對象才是我們需要的實實在在的東西,類別和實例化對像有著很密切的關係,但是在使用上類的功能是絕對不能取代實例化對象,就像模具和模具製造的產品的關係,二者的用途是不相同的。

有上面程式碼我們可以看到,this指標在java語言裡只能在實例化物件裡使用,this指標等於這個被實例化好的對象,而this後面加上點操作符,點操作符後面的東西就是this所擁有的東西,例如:姓名,工作,手,腳等等。

其實javascript裡的this指標邏輯上的概念也是實例化物件,這一點和java語言裡的this指標是一致的,但是javascript裡的this指標卻比java裡的this難以理解的多,究其根本原因我個人覺得有三個原因:

原因一:javascript是一個函數編程語言,怪就怪在它也有this指針,說明這個函數編程語言也是物件導向的語言,說的具體點,javascript裡的函數是一個高階函數,程式語言裡的高階函數是可以當作物件傳遞的,同時javascript裡的函數還有可以當作建構函數,這個建構函數可以建立實例化對象,結果導致方法執行時候this指標的指向會不斷變化,很難控制。

原因二:javascript裡的全域作用域對this指標有很大的影響,由上面java的例子我們看到,this指標只有在使用new運算元後才會生效,但javascript裡的this在沒有進行new操作也會生效,這時候this往往會指向全域物件window。

原因三:javascript裡call和apply操作符可以隨意改變this指向,這看起來很靈活,但是這種不合常理的做法破壞了我們理解this指針的本意,同時也讓寫代碼時候很難理解this的真正指向



#上面的三個原因都違反了傳統this指標所使用的方法,它們都擁有有別於傳統this原理的理解思路,而在實際開發里三個原因又往往會交織在一起,so,this,雲裡霧裡了……


入門書:professionnal Javascript for web devolopers,--高階的說法是這樣的:

##this總是指向呼叫該方法的對象!



var name="zhoulujun";

function say(){

#console.log(this.name)

}

say(); //zhoulujun



#在script標籤裡我們可以直接使用this指針,this指標(指向window對象,結果)就是window物件,即使使用三等號它們也是相等的。全域作用域常常會幹擾我們很好的理解javascript語言的特性,這種幹擾的本質就是:

在javascript語言裡全域作用域可以理解為window物件,記住window是物件而不是類,也就是說window是被實例化的物件,這個實例化的過程是在頁面載入時候由javascript引擎完成的,整個頁面裡的要素都被濃縮到這個window對象,因為程式設計師無法透過程式語言來控制和操作這個實例化過程,所以開發時候我們就沒有建構這個this指針的感覺,常常會忽略它,這就是乾擾我們在程式碼裡理解this指針指向window的情形。

這裡this指向window對象,所以this.name->zhoulujun!



當執行say函數的時候, JavaScript 會建立一個Execute context (執行上下文),執行上下文中就包含了say函數運行期所需的所有資訊。 Execute context 也有自己的 Scope chain, 當函數運行時, JavaScript 引擎會先從用 say函數的作用域鏈來初始化執行上下文的作用域鏈。

這裡可以大致記下:

var myObj={
name:"zhoulujun",
fn:function(){
console.log(this.name)
}
};
myObj.fn();
登入後複製



#

这里的this指向obj,因为fn()运行在obj里面……

然后再来看……

var name="zhoulujun";
function say(){
console.log(this.name)
console.log(this)
}
say();
function say2(){
var site="zhoulujun.cn";
console.log(this.site);
}
say2();
登入後複製


myObj2={
site:"zhoulujun.cn",
fn:function(){
console.log(this.site)
}
}
登入後複製



这里的this指向的是对象myObj2,因为你调用这个fn是通过myObj2.fn()执行的,那自然指向就是对象myObj2,这里再次强调一点,this的指向在函数创建的时候是决定不了的,在调用的时候才能决定,谁调用的就指向谁,一定要搞清楚这个

然后,我们更深入(受不了 …………

myObj3={
site:"zhoulujun.cn",
andy:{
site:"www.zhoulujun.cn",
fn:function(){
console.log(this.site)
}
}
};
myObj3.andy.fn();
登入後複製



这里同样也是对象Object点出来的,但是同样this并没有执行它,那你肯定会说我一开始说的那些不就都是错误的吗?其实也不是,只是一开始说的不准确,接下来我将补充一句话,我相信你就可以彻底的理解this的指向的问题。

如果,你实在理解不了,就这么样背下来吧!

情况1:如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window,但是我们这里不探讨严格版的问题,你想了解可以自行上网查找。

情况2:如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。

情况3:如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,如果不相信,那么接下来我们继续看几个例子。

这样既对了吗??深入点(就受不了了……讨厌……

myObj3={
site:"zhoulujun.cn",
andy:{
site:"www.zhoulujun.cn",
fn:function(){
console.log(this)
console.log(this.site)
}
}
};
//    myObj3.andy.fn();
var fn=myObj3.andy.fn;
fn();
登入後複製



其实,这里的 fn等价于

fn:function(age){

console.log(this.name+age);

}

下面我们来聊聊函数的定义方式:声明函数和函数表达式

我们最上面第一个案例定义函数的方式在javascript语言称作声明函数,第二种定义函数的方式叫做函数表达式,这两种方式我们通常认为是等价的,但是它们其实是有区别的,而这个区别常常会让我们混淆this指针的使用,我们再看看下面的代码:



為什麼say可以執行,say3不可以?那是因為:



為什麼say3印出結果是undefined,我在前文裡講到了undefined是在記憶體的堆疊區已經有了變數的名稱,但是沒有堆疊區的變數值,同時堆疊區是沒有特定的物件,這是javascript引擎在預先載入掃描變數定義所致,但是ftn01的列印結果很令人意外,既然列印出完成的函數定義了,而且程式碼並沒有按順序執行,這只能說明一個問題:

在javascript語言透過聲明函數方式定義函數,javascript引擎在預處理過程裡就把函數定義和賦值操作都完成了,在這裡我補充下javascript裡預處理的特性,其實預處理是和執行環境相關,在上篇文章裡我講到執行環境有兩大類:全局執行環境和局部執行環境,執行環境是透過上下文變數體現的,其實這個過程都是在函數執行前完成,預處理就是構造執行環境的另一個說法,總而言之預處理和構造執行環境的主要目的就是明確變量定義,分清變數的邊界,但是在全域作用域構造或者說全域變數預處理時候對於宣告函數有些不同,宣告函數會將變數定義和賦值運算同時完成,因此我們看到上面程式碼的運行結果。由於宣告函數都會在全域作用域建構時候完成,因此宣告函數都是window物件的屬性,這就說明為什麼我們不管在哪裡宣告函數,宣告函數最終都是屬於window物件的原因了。

這裡推薦看下-java一個類別的執行順序:

http://www.zhoulujun.cn/zhoulujun/html/java/javaBase/7704.html



#

其实在javascript语言里任何匿名函数都是属于window对象,它们也都是在全局作用域构造时候完成定义和赋值,但是匿名函数是没有名字的函数变量,但是在定义匿名函数时候它会返回自己的内存地址,如果此时有个变量接收了这个内存地址,那么匿名函数就能在程序里被使用了,因为匿名函数也是在全局执行环境构造时候定义和赋值,所以匿名函数的this指向也是window对象,所以上面代码执行时候fn的this都是指向window,因为javascript变量名称不管在那个作用域有效,堆区的存储的函数都是在全局执行环境时候就被固定下来了,变量的名字只是一个指代而已。

类似的情况(面试题喜欢这么考!)……比如:



this都是指向实例化对象,前面讲到那么多情况this都指向window,就是因为这些时候只做了一次实例化操作,而这个实例化都是在实例化window对象,所以this都是指向window。我们要把this从window变成别的对象,就得要让function被实例化,那如何让javascript的function实例化呢?答案就是使用new操作符。

再来看 构造函数:

function  User(){
this.name="zhoulujun";
console.log(this);
}
var andy=new User();
console.log(andy.name)
登入後複製



why andy 的name 是 zhoulujun,那是:因为:

new关键字可以改变this的指向,将这个this指向对象andy,

那andy什么时候又成了思密达,oh,no,is Object?

因為用了new關鍵字就是建立一個物件實例(重要的事情默讀三遍)

我們這裡用變數andy建立了一個User使用者實例(相當於複製了一份User到物件andy裡面),此時僅只是創建,並沒有執行,而呼叫這個函數User的是物件andy,那麼this指向的自然是物件andy,那麼為什麼物件User中會有name,因為你已經複製了一份User函數到物件andy中,用了new關鍵字就等於複製了一份。

java 程式猿: Class user=new User();似曾相識木有…



function既是函數又可以表示對象,function是函數時候還能當做建構函數,javascript的建構子我常認為是把類別和建構子合而為一,當然在javascript語言規範裡是沒有類別的概念,但是我這種理解可以當作建構子和普通函數的一個差別,這樣理解起來會更容易

下面我貼出在《javascript高階程式設計》裡對new運算元的解釋:

new運算子會讓建構子產生以下變更:

# 1.       建立一個新物件;

2.       將建構函數的作用域賦給新物件(因此this就指向了這個新物件);

##3.       執行建構函式中的程式碼(為這個新物件新增屬性);######

4. 返回新对象

……

妈的:读的那么拗口,不明觉厉…………看图……还不

不明白……

var myObj5={
name:"andy"
};
var myObj6=new Object();
myObj6.name="andy";
function  say5(name){
console.log(name)
}
var say6=new Function("name","console.log(name)");
console.log(myObj5)
console.log(myObj6)
say5("andy");
say6("andy");
登入後複製



还不明白,就请奶奶买块豆腐,撞死算了……

第四点也要着重讲下,记住构造函数被new操作,要让new正常作用最好不能在构造函数里写return,没有return的构造函数都是按上面四点执行,有了return情况就复杂了

return这王八蛋……



那么我这样呢……



does it have to be like this?Tell me why(why),is there something I have missed?

Tell me why(why),cos I don't understand…………



那是因为……because of u?no return……



所以:如果返回的是基本类型,就会丢掉…只能返回Object类型……typeof xx ===“object”

看到called 没有?什么鬼!!

其实new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。

var a={
name:"andy",
site:"zhoulujun.cn",
fn:function(age){
console.log(this.name+age);
}
};
var b={
name:"zhoulujun",
site:"www.zhoulujun.cn",
fn:function(age){
console.log(this.name+age);
}
};
a.fn(2); //andy2
a.fn.call(b,2) //zhoulujun2
a.fn.apply(b,[2])//zhoulujun2
当然,还有bind……
var arr = [1, 2];
var add = Array.prototype.push.bind(arr, 3);
// effectively the same as arr.push(3)
add();
// effectively the same as arr.push(3, 4)
add(4);
console.log(arr);
// <- [1, 2, 3, 3, 4]
在下面的例子,this将无法在作用域链中保持不变。这是规则的缺陷,并且常常会给业余开发者带来困惑。
function scoping () {
console.log(this);
return function () {
console.log(this);
};
}
scoping()();
// <- Window
// <- Window
有一个常见的方法,创建一个局部变量保持对this的引用,并且在子作用域中不能有同命变量。子作用域中的同名变量将覆盖父作用域中对this的引用。
function retaining () {
var self = this;
return function () {
console.log(self);
};
}
retaining()();
// <- Window
除非你真的想同时使用父作用域的this,以及当前this值,由于某些莫名其妙的原因,我更喜欢是使用的方法.bind函数。这可以用来将父作用域的this指定给子作用域。
function bound () {
return function () {
console.log(this);
}.bind(this);
}
bound()();
// <- Window
登入後複製

写到这里,都看不下去,逻辑有点混乱,有的是从前辈哪里引用的。

改天有时间整理下,然后,在去讲下闭包(……closer





#

以上是深入理解javascript中this指針的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板