閉包
變量作用域
- 變量作用域的概念:就是一個變量可以使用的范圍
- JS中首先有一個最外層的作用域:稱之為全局作用域
- JS中還可以通過函數創建出一個獨立的作用域,其中函數可以嵌套,所以作用域也可以嵌套
var age=18; //age是在全局作用域中聲明的變量:全局變量
function f1(){
console.log(name); //可以訪問到name變量
var name="周董" //name是f1函數內部聲明的變量,所以name變量的作用域就是在f1函數內部
console.log(name); //可以訪問到name變量
console.log(age); //age是全局作用域中聲明的,所以age也可以訪問
}
//多級作用域
//-->1級作用域
var gender="男";
function fn(){
//gender:可以訪問
//age: 可以訪問,值為undefined
//height: 不能訪問
//-->2級作用域
return function(){
//gender: 通過一級一級作用域的查找,發現gender是全局作用域中聲明的變量
//age: 可以訪問,值為undefined
//height: 可以訪問,值為undefined
console.log(gender);
//-->3級作用域
var height=180;
}
var age=5;
}
- 注意:變量的聲明和賦值是在兩個不同時期的
function fn(){
console.log(age); //undeinfed
var age=18;
console.log(age); //18
}
- fn函數執行的時候,首先找到函數內部所有的變量、函數聲明,把他們放在作用域中,給變量一個初始值:undefined -->變量可以訪問
- 逐條執行代碼,在執行代碼的過程中,如果有賦值語句,對變量進行賦值
作用域鏈
- 由於作用域是相對於變量而言的,而如果存在多級作用域,這個變量又來自於哪里?我們把這個變量的查找過程稱之為變量的作用域鏈
- 作用域鏈的意義:查找變量(確定變量來自於哪里,變量是否可以訪問)
- 簡單來說,作用域鏈可以用以下幾句話來概括:(或者說:確定一個變量來自於哪個作用域)
- 查看當前作用域,如果當前作用域聲明了這個變量,就確定結果
- 查找當前作用域的上級作用域,也就是當前函數的上級函數,看看上級函數中有沒有聲明
- 再查找上級函數的上級函數,直到全局作用域為止
- 如果全局作用域中也沒有,我們就認為這個變量未聲明(xxx is not defined)
- 再查找上級函數的上級函數,直到全局作用域為止
- 查找當前作用域的上級作用域,也就是當前函數的上級函數,看看上級函數中有沒有聲明
- 查看當前作用域,如果當前作用域聲明了這個變量,就確定結果
function fn(callback){
var age=18;
callback()
}
fn(function(){
console.log(age); //undefined
var age = 15;
//分析:age變量:
//1、查找當前作用域:並沒有
//2、查找上一級作用域:全局作用域
//-->難點:看上一級作用域,不是看函數在哪里調用,而是看函數在哪里編寫
//-->因為這種特別,我們通常會把作用域說成是:詞法作用域
})
閉包概念
各種專業文獻上的"閉包"(closure)定義非常抽象,很難看懂。我的理解是,閉包就是能夠讀取其他函數內部變量的函數。
由於在Javascript語言中,只有函數內部的子函數才能讀取局部變量,因此可以把閉包簡單理解成"定義在一個函數內部的函數"。
所以,在本質上,閉包就是將函數內部和函數外部連接起來的一座橋梁。
閉包的用途
閉包可以用在許多地方。它的最大用處有兩個,一個是可以讀取函數內部的變量,另一個就是讓這些變量的值始終保持在內存中。
使用閉包的注意點
- 由於閉包會使得函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致內存泄露。解決方法是,在退出函數之前,將不使用的局部變量全部刪除。
- 閉包會在父函數外部,改變父函數內部變量的值。所以,如果你把父函數當作對象(object)使用,把閉包當作它的公用方法(Public Method),把內部變量當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數內部變量的值。
閉包問題的產生原因
- 函數執行完畢后,作用域中保留了最新的a變量的值
閉包的應用場景
- 模塊化
- 防止變量被破壞
函數的4種調用方式
- 在
ES6之前
,函數內部的this是由該函數的調用方式決定的- 函數內部的this跟大小寫、書寫位置無關
- 在ES6的箭頭函數之前的時代,想要判斷一個函數內部的this指向誰,就是根據上面的四種方式來決定的
- 1、函數調用
var age=18;
var p={
age:15
say:function(){
console.log(this.age);
}
}
var f1=p.say; //f1是函數
f1(); //函數調用-->this:window -->this.age=18
function Person(name){
this.name=name;
}
Person.prototype={
constructor:Person,
say:function(){
console.log(this.name);
}
}
//函數的第一種調用方式:函數調用
// -->函數內部的this指向window
Person("abc"); //window.name --> abc
//注:window對象中的方法都是全局函數,window對象中的屬性都是全局變量
- 2、方法調用
var clear=function(){
console.log(this.length);
}
var length=50;
var tom={ c:clear,length:100 };
tom.c(); //這里是方法調用的方式
//打印this.length 是50 還是100?
//-->相當於:this是指向window還是指向tom呢?
// -->結果為:100
// -->this:tom
//結論:由於clear函數被當成tom.c()這種方法的形式來進行調用,所以函數內部的this指向調用該方法的對象:tom
- 3、new調用(構造函數)
//1、
function fn(name){
this.name=name;
}
//通過new關鍵字來調用的,那么這種方式就是構造函數的構造函數的調用方式,那么函數內部的this就是該構造函數的實例
var _n=new fn("小明"); //_n有個name屬性,值為:小明
//2、
function jQuery(){
var _init=jQuery.prototype.init;
//_init就是一個構造函數
return new _init();
}
jQuery.prototype={
constructor:jQuery,
length:100,
init:function(){
//this可以訪問到實例本身的屬性,也可以訪問到init.prototype中的屬性
//這里的init.prototype並不是jQuery.prototype
console.log(this.length); // undefined
}
}
-
4、上下文調用方式(call、apply、bind)
-
上下文模式應用場景:
- 一些需要指定this的情況,比如$.each方法回調函數內部的this
- 判斷數據類型:
- Object.prototype.toString.call(1);
-
call、apply
//call、apply function f1(){ console.log(this); } //call方法的第一個參數決定了函數內部的this的值 f1.call([1,3,5]) f1.call({age:20,height:1000}) f1.call(1) f1.call("abc") f1.call(true); f1.call(null) f1.call(undefined); //上述代碼可以用apply完全替換 //總結: //call方法的第一個參數: //1、如果是一個對象類型,那么函數內部的this指向該對象 //2、如果是undefined、null,那么函數內部的this指向window //3、如果是數字-->this:對應的Number構造函數的實例 // --> 1 --> new Number(1) //4、如果是字符串-->this:String構造函數的實例 // --> "abc" --> new String("abc") //5、如果是布爾值-->this:Boolean構造函數的實例 // --> false --> new Boolean(false)
- call和apply異同:
- call和apply都可以改變函數內部的this的值
- 不同的地方:傳參的形式不同
function toString(a,b,c){ console.log(a+" "+b+" "+c); } toString.call(null,1,3,5) //"1 3 5" toString.apply(null,[1,3,5]) //"1 3 5"
- bind
var obj = { age:18, run : function(){ console.log(this); //this:obj var _that=this; setTimeout(function(){ //this指向window console.log(this.age); //undefined是正確的 console.log(_that.age); //18 },50); } } obj.run(); //bind是es5中才有的(IE9+) var obj5 = { age:18, run : function(){ console.log(this); //this:obj5 setTimeout((function(){ console.log(this.age); //18 }).bind(this),50); //this:obj5 //通過執行了bind方法,匿名函數本身並沒有執行,只是改變了該函數內部的this的值,指向obj5 } } obj5.run(); //bind基本用法 function speed(){ console.log(this.seconds); } //執行了bind方法之后,產生了一個新函數,這個新函數里面的邏輯和原來還是一樣的,唯一的不同是this指向{seconds:100} var speedBind = speed.bind({ seconds:100 }); speedBind(); //100 (function eat(){ console.log(this.seconds); }).bind({ seconds:360 })() //360 var obj={ name:"西瓜", drink:(function(){ //this指向了:{ name:"橙汁" } console.log(this.name); }).bind({ name:"橙汁" }) } obj.drink(); //"橙汁" var p10={ height:88, run:function(){ //this setInterval((function(){ console.log(this.height); //88 }).bind(this),100) } } p10.run(); ``
-