匿名函式以及閉包內部this指向(函式呼叫模式的問題)
網上看到一句話,匿名函式的執行是具有全域性性的,那怎麼具有的全域性性呢?閉包內部this的指向是window,為什麼指向了window呢?下面通過js函式呼叫模式和部分案例分析了為什麼確實如此
1.js函式呼叫的模式
1.方法呼叫模式和函式呼叫模式
- 如果一個函式被設定為一個物件的屬性,則稱它為一個方法。當通過物件對其進行呼叫時,即this的方法呼叫模式。在方法呼叫模式下,函式中的this指向該函式所屬的物件。
- 當一個函式並非物件的屬性,而是直接作為函式進行呼叫時,為函式呼叫模式。此模式來呼叫函式的時候,this繫結的是全域性物件。這是語言設計的一個錯誤。倘若語言設計正確,那麼當內部函式被呼叫時,this應該仍然繫結到外部函式的this變數。這個設計錯誤的後果就是方法不能利用內部函式來幫助它工作,因為內部函式的this被綁定了錯誤的值,所以不能共享該方法對物件的訪問權
var obj = { val : 1, show : function(){alert(this.val);},//方法呼叫模式 outFunc : function(){ function innerFunc(){ console.log(this); } innerFunc(); //函式呼叫模式 } }; obj.innerFunc(); //在嚴格模式下,console.log(this)中的this為undefined, //否則,console.log(this)中的this為全域性物件(瀏覽器中為window) //下文講解為什麼
2.構造器呼叫模式
當以new來呼叫一個函式時,即構造器模式。
當使用new呼叫時,發生這些事情:
建立一個連線到該函式prototype的新物件
將this繫結到建立的新物件上
函式結束時,如果沒有返回其它物件,就返回this,即新建立的物件。
var quo=function(string){ this.status=string; } quo.prototype.get_status=function(){ return this.status; } var qq=new quo("aaa"); alert(qq.get_status());
3.上下文呼叫模式(call,apply)
var myobject={}; var sum = function(a,b){ return a+b; }; var sum2 = sum.call(myobject,10,30); //var sum2 = sum.apply(myobject,[10,30]); alert(sum2);
2.閉包和匿名函式案例分析(這兩種都是函式呼叫模式)
var person = { name :'one', sayOne:function () {//方法呼叫模式 console.log(this.name) }, sayTwo:function () {//函式呼叫模式 return function () { console.log(this.name) } }, wrap: function(){//函式呼叫模式,匿名函式的執行環境具有全域性性的由來 (function (){ console.log(this.name) })() } } person.sayOne()//onesayOne呼叫者是person物件,所以this指向person;(方法呼叫模式) person.sayTwo()()//window 返回的匿名函式在全域性執行所以是window(函式呼叫模式) person.wrap()//window 語言就是這樣設計的,也是匿名函式具有全域性性的由來(函式呼叫模式)
3.事件監聽內部呼叫方法案例分析
var div=document.getElementById("one"); function f2(){ console.log(this) } div.onclick =function () { console.log(this)//one那個節點 f2()//window(函式呼叫模式) }
4.綜合應用:函式節流案例分析
1.throttle函式的執行環境具有全域性性,內部this通常是指向window的,然後返回一個匿名函式。
2.返回的匿名函式綁定了事件,this指向監聽的元素(document)
3.fn其實與上面返回匿名函式形成了閉包,且fn也其實是一個匿名函式,匿名函式的執行具有全域性性,fn內部this應該指向window
4這裡用apply修正this指向,使fn內部的this重新指向document
function throttle(fn, delay) { console.log(this)//window // 記錄上一次函式觸發的時間 var lastTime = 0; return function() { // 記錄當前函式觸發的時間 var nowTime = Date.now(); if(nowTime - lastTime > delay) { /* fn(); console.log(this)//document */ fn.apply(this)// 修正this指向問題 console.log(this)//document // 同步時間 lastTime = nowTime; } } } document.onscroll = throttle(function() { /*console.log(this)//window*/ console.log(this)//document console.log('scroll事件被觸發了' + Date.now()) }, 1000)
5.參考JS高階程式設計的案例解決問題
var name = "global"; var foo = { name: "foo", getName : function(){ console.log(this.name); } } var bar = { name: "bar", getName : function(){ return (function(){ console.log(this.name); })(); } } foo.getName(); //foo foo.getName.call(bar); //bar foo.getName.call(this); //global foo.getName.call(window); //global (function(){ console.log(this.name) }.bind(bar))(); //bar (function(){ console.log(this.name) }.bind())(); //global