與閉包有關的概念:變數的作用域和變數的生存週期。以下這篇文章就來跟大家介紹一下JavaScript中變數的作用域及閉包,有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。
一、變數的作用域
#1、變數的作用域指變數有效的範圍,與變數定義的位置密切相關,作用域是從空間的角度來描述變數的,也可以理解為變數的可見性。在某個範圍內變數是可見的,也就是說,變數是可用的。 【相關課程推薦:JavaScript影片教學】
2、依作用域的不同,變數可分為全域變數和局部變數。
● 全域變數:在全域環境中宣告的變數
● 局部變數:在函數中宣告的變數
● 當函數在執行時,會建立一個封閉的執行期上下文環境,函數內部聲明的變數僅可在函數內部使用,外部無法訪問,而全局變數則在任何地方都可以使用
3、在函數中使用var關鍵字顯示聲明的變數是局部變數;而沒有用var關鍵字,用直接賦值的方式宣告的變數是全域變數
var m=8; function f1(){ var a1=10; console.log(m); //8 } function f2(){ var a2=20; console.log(m); //8 } f1(); f2();
4、依賴變數作用域實現封裝特性
(1)使用ES6提供的let
(2)透過函數來建立作用域:
var myObject=(function(){ var _name="yian"; //私有变量 return { getName:function(){ //公有方法 return _name; } } })(); console.log(myObject._name); //undefined console.log(myObject.getName()); //yian
#二、變數的生存週期
##1、對於全域變數來說,其生命週期是永久的,除非主動銷毀此全域變數;2、對於在函數內以var關鍵字宣告的局部變數來說,當退出函數時,這些局部變數即失去它們的價值,它們會隨著函數呼叫的結束而被銷毀3、模仿區塊級作用域(1)用作區塊級作用域的匿名函數:將函數宣告包含在一對圓括號中,表示它實際上是一個函數表達式,而緊接在後的另一對圓括號會立即呼叫這個函數。(function(){ //这里是块级作用域 })();在匿名函數中定義的任何變量,都會在執行結束時被銷毀(2)先定義一個函數,然後呼叫它。定義函數的方式是創建一個匿名函數,並將這個匿名函數賦值給變數;而呼叫函數的方式是在函數的名稱後面加上一對圓括號。
var someFunction=function(){ //这里是块级作用域 }; someFunction();
經典問題:
var nodes=document.getElementsByTagName("div"); for(var i= 0,len=nodes.length;i<len;i++){ nodes[i].onclick=function(){ console.log(i); //无论点击哪个div,最后弹出的结果都是5 } }
解釋: div節點的onclick事件是被非同步觸發的,當事件被觸發時,for循環早已結束,此時i 已經是5
解決方法:
#法一:使用ES6中的let法二:在閉包的幫助下,把每次循環的i值都封存起來var nodes=document.getElementsByTagName("div"); for(var i= 0,len=nodes.length;i<len;i++){ (function(x){ nodes[i].onclick=function(){ console.log(x); } })(i); }4、作用:讀取函數內部的變量,並將這些變數的值始終保存在記憶體中#( 1)封裝變數:閉包可以把一些不需要暴露在全域的變數封裝為“私有變數”,私有變數包括函數的參數、局部變數和其他在函數內部定義的函數。 範例:mult函數接收number類型的參數,並傳回這些參數的乘積、#初始程式碼:
var cache={ }; var mult=function(){ var args=Array.prototype.join.call(arguments,","); if(cache[args]){ return cache[args]; } var a=1; for(var i=0,len=arguments.length;i<len;i++){ a=a*arguments[i]; } return cache[args]=a; }; console.log(mult(1,2,3)); //6 console.log(mult(3,4,5)); //60使用閉包後:
var mult=(function(){ var cache={ }; //加入缓存机制,避免相同参数的计算 var calculate=function(){ var a=1; for(var i= 0,len=arguments.length;i<len;i++){ a=a*arguments[i]; } return a; }; return function(){ var args=Array.prototype.join.call(arguments,","); if(args in cache){ return cache[args]; } return cache[args]=calculate.apply(null,arguments); } })();補充:in判斷屬性屬於物件
var mycar = {make: "Honda", model: "Accord", year: 1998}; if ( "make" in mycar ){ //属性名必须是字符串形式,因为make不是一个变量 document.write('true'); // 显示true } else{ document.write('false'); }(2)延續局部變數的壽命#例:使用report資料上報時會遺失30%左右的數據,原因是img時report中的局部變量,當report函數呼叫結束後,img局部變數隨即被銷毀初始程式碼:
var report=function(src){ var image=new Image(); image.src=src; };使用閉包後(把img變數用閉包封裝起來):
var report=(function(){ var imgs=[ ]; return function(){ var image=new Image(); imgs.push(image); image.src=src; } })();5、閉包與物件導向設計閉包寫入法:
var extent=function(){ var value=0; return { call:function(){ value++; console.log(value); } } } var extent=extent(); extent.call(); //1 extent.call(); //2物件導向寫法一:
var extend={ value:0, call:function(){ this.value++; console.log(this.value); } }; extend.call(); //1 extend.call(); //2物件導向寫法二:
var Extend=function(){ this.value=0; }; Extend.prototype.call=function(){ this.value++; console.log(this.value); }; var extend=new Extend(); extend.call(); //1 extend.call(); //26、閉包與記憶體管理● 局部變數變數應該在函數退出時被解除引用,但如果局部變數被封閉在閉包形成的環境中,那麼局部變數就會一直生存下去,即它會常駐記憶體。 ● 使用閉包的同時比較容易形成循環引用,如果閉包的作用域鏈中保存著一些DOM節點,這就有可能造成記憶體洩漏。 ● 解決循環引用帶來的記憶體洩漏問題:把循環引用中的變數設為null。 (將變數設為null以為切斷變數與它之前引用的值之間的連接,當垃圾收集器下次運行時,就會刪除這些值並回收它們佔用的記憶體)7、特點:● 函數巢狀函數;● 在函數內部可引用外部的參數與變數;
##● 參數和變數不會以垃圾回收機制回收。
8、優點:避免全域變數的污染
9、缺点:会常驻内存,增加内存的使用量,使用不当会造成内存泄漏;闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存
10、创建闭包
写法一:
function a() { var b=123; function c(){ console.log(b+=1); } return c; } var d=a(); d();
方式二:
function f1(){ var num=10; //函数执行完毕,变量仍然存在 var f2=function(){ num++; console.log(num); //11 }; return f2; } var res=f1(); res();
● 解释:执行f1()后,f1()闭包内部的变量会存在,而闭包内部函数的内部变量不会存在,使得JavaScript的垃圾回收机制不会收回f1()占用的资源,因为f1()中内部函数的执行需要依赖f1()中的变量。
方式三:
function foo(x) { var tmp = 3; return function f2(y) { alert(x + y + (++tmp)); //17 }; } var bar = foo(3); //bar现在是一个闭包 bar(10);
练习题:
function f1(){ var a=1; t=function(){ a++; } return function(){ console.log(a); } } var b=f1(); //返回值为一个匿名函数 b(); //1 t(); b(); //2
声明变量,若变量名称相同,就近原则:
var name="g"; function out(){ var name="loc"; function foo(){ console.log(name); } foo(); } out(); //name=loc
补充知识点:
1、JS中有哪些垃圾回收机制?
(1)引用计数:跟踪记录每个值被使用的次数。
● 当声明一个变量并将一个引用类型赋值给该变量时,该值的引用次数加1;
● 若该变量的值变为另一个,则该值引用次数减1;
● 若该值引用次数为0时,说明变量没有在使用,此值无法访问;
● 因此,可以将它占用的空间回收,垃圾回收机制会在运行时清理引用次数为0 的值所占用的空间。
● 在低版的IE中会发生内存泄漏,很多时候就是因为它采用引用计数得到方式进行垃圾回收(如果两个对象之间形成了循环引用,那么这两个对象都无法被回收)。
(2)标记清除:最常见的垃圾回收方式
● 当变量进入执行环境时,垃圾回收器将其标为“进入环境”,离开时标记为“离开环境”;
● 垃圾回收机制在运行时给存储在内存中的所有变量加上标记;
● 去掉环境中的变量及被环境中变量所引用的变量(闭包)的标记;
● 完成这些后仍存在的标记就是要删除的变量。
2、哪些操作会造成内存泄漏?
● 内存泄漏:指不再拥有或需要任何对象(数据)之后,它们仍然存在于内存中。
● 垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为0(没有其他对象引用过该对象),或对该对象的唯一引用是循环的,那么该对象占用的内存立即被回收。
● 如果setTimeout的第一个参数使用字符串而非函数,会造成内存泄漏。
● 闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)等会造成内存泄漏。
本文来自 js教程 栏目,欢迎学习!
以上是淺談JavaScript變數的作用域及閉包的詳細內容。更多資訊請關注PHP中文網其他相關文章!