1.this默認指向函數本身。數組
--任何狀況下,this都不會默認指向函數本身,除非使用bind綁定的方式修改this爲函數本身。瀏覽器
2.this指向函數做用域或上下文對象。bash
--須要明確,任何狀況下,this都不默認指向函數的詞法做用域或上下文對象,做用域或者說上下文對象確實與對象相似,可見的標識符都是其屬性,可是該對象只存在於js引擎內部,沒法在js環境下被訪問。閉包
本質上,做用域工做模型分兩種,一種是詞法做用域,一種是動態做用域。app
1.詞法做用域: 詞法做用域指的是在詞法階段產生的做用域,由書寫者在寫代碼時所寫的變量及做用域的位置所決定。引擎根據這些位置信息來查找標識符即變量的位置。 例如:不管函數在哪裏、如何被調用,它的詞法做用域都只由被聲明時所處的位置決定。函數
2.動態做用域: 動態做用域是一個在運行時被動態肯定的形式,而不是在靜態時被肯定。動態做用域不關心函數與做用域如何嵌套或何處聲明,只關心它們在何處調用,也就是說。它的做用域鏈是基於調用棧而非做用域嵌套。 例:工具
function foo(){
console.log(a);
}
function bar(){
var a=3;
foo();
}
var a=2;
bar();
複製代碼
若是是詞法做用域,根據做用域規則,最終打印爲2; 但是動態做用域會順着調用棧去尋找變量,因此打印結果爲3。學習
js的做用域規則屬於詞法做用域規則。ui
而this的機制與動態做用域的機制相近。this在函數運行時綁定,不在編寫時綁定,其上下文取決於調用時的條件。this綁定與函數聲明位置無關,取決於函數調用方式。this
當一個函數被調用時,建立一個活動記錄(也稱執行上下文對象),此記錄對象包含函數調用棧、調用方式、傳入參數等信息,this是這個記錄的一個屬性。
調用棧,其實就是函數的調用鏈,而當前函數的調用位置就在調用棧的倒數第二個位置(瀏覽器開發者工具中,給某函數第一行打斷點debugger,運行時,能夠展現調用列表call stack) 。 示例:
//全局做用域下
function func(val) {
if(val <= 0) return;
console.log(val);
func(val-1);
}
func(5);
複製代碼
執行棧用來存儲運行時的執行環境。固然,棧遵循先進後出的規則。
上面代碼的執行棧以下: 執行建立時:建立全局執行環境 => func(5) => func(4) => func(3) => func(2) => func(1)。
執行完畢銷燬時:func(1) => func(2) => func(3) => func(4) => func(5) => 建立全局執行環境。
產生於獨立函數調用時,能夠理解爲沒法應用其餘規則時的默認規則。默認綁定下的this在非嚴格模式的狀況下,默認指向全局的window對象,而在嚴格模式的狀況下,則指向undefined。 示例:直接調用函數自己就是默認綁定
function func(){
console.log('this',this);
}
func();//this,Window...
複製代碼
function func(){
'use strict'
console.log('this',this);
}
func();//this,undefined
複製代碼
ps1:如下規則,都是以函數環境爲前提的,也就是說,this是放在函數體內執行的。在非函數環境下,也就是瀏覽器的全局做用域下,不管是否嚴格模式,this將一直指向window。一個冷知識:瀏覽器環境下的全局對象是window,其實除此以外還有一個特別的關鍵字,globalThis,在瀏覽器環境下打印該對象,指向window。 (ps1的觀點要感謝@茹挺進大佬的特別指出。)
//全局做用域下
console.log(this);//window
複製代碼
//全局做用域下
'use strict'
console.log(this);//window
複製代碼
function func(){
console.log(globalThis);
}
func();//window
複製代碼
ps2: this所在的詞法做用域在編寫或聲明時添加了"use strict",那麼,運行時this指向undefined,可是,若是this所在的函數做用域中並未添加"use strict",而運行或調用該函數的詞法做用域裏有添加,那麼也不影響,依然指向window。
function func(){
'use strict'
console.log('this',this);
}
func();//this,undefined
複製代碼
function func(){
console.log('this',this);
}
function bar(){
'use strict'
func();
}
bar();//this,window
複製代碼
ps3:對於JS代碼中沒有寫執行主體的狀況下,非嚴格模式默認都是window執行的,因此this指向的是window,可是在嚴格模式下,若沒有寫執行主體,this指向是undefined;
判斷調用位置是否有上下文對象或者說是否有執行主體。簡單說,一個對象調用了它所"擁有"的方法,那麼,這個方法中的this將指向這個對象(對象屬性引用鏈中只有上一層或者說最後一層纔在調用位置中起做用,例:a.b.c.func(),func中的this只會指向c對象)。
var obj = {
name:'myself',
func:function (){
console.log(this.name);
}
}
obj.func();//myself
複製代碼
說到對象與其包含的函數方法的關係,一般人們一提到方法,就會認爲這個函數屬於一個對象 ,這是一個誤解,函數永遠不會屬於某個對象,儘管它是對象的方法。其中存在的關係只是引用關係。 示例1:
//在對象的屬性上聲明一個函數
var obj = {
foo:function func(){}
}
複製代碼
示例2:
//獨立聲明一個函數而後用對象的屬性引用
function func(){}
var obj = {
foo:func
}
複製代碼
上述兩個例子效果是同樣的,沒有任何本質上的區別,很明顯,函數屬於它被聲明時所在的做用域;咱們都知道函數本質上是被存儲在堆內存中,而函數的引用地址被存放在棧內存中方便咱們取用,那麼實際上對象中的屬性持有的只是存在棧內存裏函數的地址引用。
若是非要把持有引用地址當成一種屬於關係的話,一個函數的地址能夠被無數變量引用持有,那麼這全部的變量都算是擁有這個函數,然而,屬於關係是惟一的,因此該觀點並不成立。
示例1:
var b = {
func:function(){}
}
var a=b.func;
a();
複製代碼
示例2:
var b = {
func:function(){}
}
function foo(fn){
fn();
}
foo(b.func)
複製代碼
這兩種狀況下,this指向丟失(不指向對象),而原理在上面的」函數方法並不屬於對象「裏已經揭露,在這裏,不管是a仍是fn(而參數傳遞其實就是一種隱式賦值,傳入函數也是),拿到的都只是函數的引用地址。
咱們修改下上面的兩個示例就一目瞭然了。
示例1:
function bar(){}
var b = {
func:bar
}
var a=b.func; //至關於 var a=bar;
a();
複製代碼
示例2:
function bar(){}
var b = {
func:bar
}
function foo(fn){
fn();
}
foo(b.func) //至關於foo(bar);
複製代碼
隱式綁定中,方法執行時,對象內部包含一個指向函數的屬性,經過這個屬性間接引用函數,從而實現this綁定。
顯式綁定也是如此,經過call,apply等方法,實現this的強制綁定(若是輸入字符串、布爾、數字等類型變量當作this綁定對象,那麼這些原始類型會被轉爲對象類型,如new String,new Boolean,new Number,這種行爲叫裝箱)。 綁定示例1:
var a = 1;
function func(){
console.log(this.a);
}
var obj = {
a:0
}
func.apply(obj);//0
複製代碼
綁定示例2:
var a = 1;
function func(){
console.log(this.a);
}
var obj = {
a:0
}
func.call(obj);//0
複製代碼
然而這依然沒法解決可能丟失綁定的問題(好比處理回調函數,因爲使用call、apply就會直接調用,而回調函數的調用沒法人爲介入控制因此回調函數上用不上call、apply)。
示例代碼:
var a = 1;
function func(){
console.log(this.a);
}
var obj = {
a:0
}
setTimeout(func.call(obj),1000);//當即執行了,沒法知足延遲執行的需求
複製代碼
bind是硬綁定,經過使用bind方法的硬綁定處理,將回調函數進行包裝,而獲得的新函數在被使用時不會丟失綁定(利用了柯理化技術,柯理化技術依託於閉包)。
示例:
var a = 1;
function func(){
console.log(this.a);
}
var obj = {
a:0
}
var newFunc = func.bind(obj);
setTimeout(newFunc,1000);//延遲1秒後打印0
複製代碼
硬綁定下降了函數的靈活性,沒法再使用隱式綁定或顯式綁定修改this。
示例:
function func(){
console.log(this.a);
}
var obj = {
a:0
}
var o = {
a:2
}
var newFunc = func.bind(obj);
newFunc.apply(o);//0
複製代碼
爲了解決靈活性的問題,咱們能夠在硬綁定的原理基礎上嘗試shim一個新的綁定方式---軟綁定。
示例:
Function.prototype.softBind = function(self){
var func = this;
var oldArg = [...arguments].slice(1)
return function (){
var newArgs = oldArg.concat([...arguments]);
var _this = (!this || this === window) ? self : this;
func.apply(_this,newArgs)
}
}
function func(){
console.log(this.a);
}
var obj = {
a:0
}
var o = {
a:2
}
var newFunc = func.softBind(obj);
newFunc();//0
newFunc.apply(o);//2
複製代碼
核心代碼:
var _this = (!this || this === window)?self:this;
//若是this綁定到全局或者undefined時,那麼就保持包裝函數softBind被調用時的綁定,不然修改this綁定到當前的新this。
複製代碼
ps:js的許多內置函數都提供了可選參數,用來實現綁定上下文對象,例:數組的forEach、map、filter等方法,第一個參數爲回調函數,第二個爲將綁定的上下文對象。
傳統語言中,構造函數是類中的一些特殊方法,使用new初始化類時會調用類中的構造函數。而js中的所謂"構造函數"其實只是普通的函數,它們不屬於某個類,也不會實例化一個類。實際上js中並不存在構造函數,只有對於函數的構造調用。 使用new調用函數(構造調用) 時,
function func(name){
this.name = name;
this.printName = function(){
console.log(this.name);
}
}
var instance = new func('myself');
instance.printName();//myself
複製代碼
function func(name){
this.name = name;
this.printName = function(){
console.log(this.name);
}
return {
name:'yourself',
printName:function(){
console.log(this.name);
}
}
}
var instance = new func('myself');
instance.printName();//yourself
複製代碼
new綁定 > 顯式綁定 > 隱式綁定 > 默認綁定。
var name = window;
function func(){
console.log(this.name);
}
var obj = {
name:'obj',
printName:func
}
obj.printName();//obj
複製代碼
var obj = {
name:'obj',
printName:function (){
console.log(this.name);
}
}
var other = {
name:'other'
}
obj.printName.apply(other);//other
obj.printName.call(other);//other
複製代碼
function func(name){
this.name = name;
}
var obj = {};
var foo = func.bind(obj);
foo('obj');
console.log(obj.name);//obj
var bar = new foo('instance');
console.log(obj.name);//obj
console.log(bar.name);//instance
複製代碼
根據該函數所在詞法做用域決定,簡單來講,箭頭函數中的this綁定繼承於該函數所在做用域中this的綁定。
var name = 'window';
var obj = {
name:'obj',
printName:()=>{
console.log(this.name);
}
}
obj.printName();//window
複製代碼
箭頭函數沒有本身的this,因此使用bind、apply、call沒法修改其this指向,其this依然指向聲明時繼承的this。
var name = 'window';
var printName = () => {
console.log(this.name);
}
var instance = {
name:'instance'
}
var callIns = printName.bind(instance);
callIns();//'window'
printName.apply(instance);//'window'
printName.call(instance);//'window'
複製代碼
雖然bind不能修改其this指向,可是依然能夠實現預參數的效果;而apply與call的參數傳遞也是生效的。
var func = (param) => {
console.log(this);
console.log(param);
}
var obj = {}
var foo = func.bind(obj,'hellow');
foo();
//window
//'hellow'
複製代碼
ps:箭頭函數不僅沒有本身this,也沒有arguments對象。
var func = (param) => {
console.log('arguments',arguments);
}
func('param');//Error,arguments is not defined
複製代碼
須要聲明的一點是,我不是一個教授者,我只是一個分享者、一個討論者、一個學習者,有不一樣的意見或新的想法,提出來,咱們一塊兒研究。分享的同時,並不僅是被分享者在學習進步,分享者亦是。
知識遍地,拾到了就是你的。
既然有用,不妨點贊,讓更多的人瞭解、學習並提高。
ps:我欣賞善意的溝通,固然,惡意的攻擊也不是不能夠,拿出可靠的依據讓我服氣,不要譁衆取寵,缺少內涵。