這篇文章主要介紹了關於對js函數的實參,形參以及閉包的理解,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下
if(a === undefined) a = [];
等價於
a = a || [];
這兩句是完全等價的,只不過後者需要提前宣告a而已
如果參數沒有傳入,其餘的填充undefined
可選的形式參數:透過註解/optional/來強調參數可選,並且要將其放在最後,否則就要使用null或undefined來作為佔位符來進行傳入
#callee為指涉目前正在執行的函數
caller指涉目前正在執行函數的函數
> > function e(o){ ... return o.Object; ... } undefined > e; [Function: e] > var a = {Object:33}; undefined > e(a); 33 >
函數能作為值傳入另外一個函數
函數屬性可以自訂
o.a = 3; function o() { return o.a; }
在函數中聲明的變數在整個函數體內都是可見的(包括在巢狀函數中),在函數外部是不可見的。不在任何函數內宣告的變數為全域變數。在整個js程式中都是可見的。在js中無法宣告只在一個程式碼區塊內可見的變數的。所以常常簡單的定義一個函數是用來當作臨時的命名空間。在這個命名空間內定義的變數都不會污染到全域命名空間。
正是因為如果將變數定義在全域的時候,會出現變數的污染,污染到全域變數(好吧,這是動態語言的坑)導致出現一些未知的錯誤。所以呢將變數放置在函數中,在進行調用,這樣放置其變數污染其全域空間,出現變數的衝突(尤其是在瀏覽器的環境下,很容易的,導致各種未知錯誤,所以必須要這樣做)
( function() { return 333; }() );
加()是必須的,因為如果不加()會讓js解釋器認為其為函數聲明,function按照函數聲明來進行解釋,js解釋器不允許創建一個匿名的函數聲明,所以會報錯。
加()變成一個函數表達式,js解釋器運行創建一個匿名的函數表達式
終於到閉包了。 (正經點Σ( ° △ °|||)︴)
(這是最難的地方,是函數式程式設計的基礎,也是能否學好js的最關鍵的地方。。。當然了,es6還有一個令人討厭的箭頭函數)
閉包是其函數式程式設計的重要的基礎
和其他語言一樣js採用的詞法作用域,即函數的執行依賴於變數的作用域,作用域是在函數定義時決定的,不是在其呼叫所決定的
即js的函數物件的內部狀態不僅包含函數的程式碼邏輯,還必須引用目前的作用域鏈(變數的作用域向下傳遞的,變數的作用域鏈在進行尋找的時候往上尋找,直到函數的頂部)函數物件可以透過作用域鏈相互關聯起來,函數體內部的變數可以保存在函數作用域內 ,即閉包
很古老滴術語,指函數變數可以被隱藏於作用域鏈之內,因此看起來函數將變數包裹起來。
作用域鍊為一個物件的列表,每次呼叫js函數的時候,都會建立一個新的物件來保存其局部變量,把這個物件添加到作用域鏈中,如果函數返回,就從作用域鏈中將綁定的對象刪除,如果不存在嵌套函數,也不存在其引用指向這個綁定的對象,會被js解釋器的垃圾回收機制不定時的回收,是不定時的,不是在沒有完全引用的時候立刻刪除,如果定義了巢狀函數,每個巢狀函數都各自對應著一個作用域鏈,並且這個作用域鏈指向一個變量綁定的物件。如果這些巢狀函數物件在外部函數中保存下來,那麼他們也會像所指向的變數綁定物件一樣當做垃圾進行回收,如果這個函數定義了巢狀的函數,並將它作為返回值返回,或者儲存在某處屬性裡,會有外部引用指向這個巢狀函數,也就是不會被當作垃圾回收,其變數所綁定的物件也不會當作垃圾回收。
函數執行完畢以後相關的作用域鏈不會刪除,只有當不在有引用的時候,才會進行刪除操作
原始堆疊
堆疊頂部window
執行下列js腳本
function a() { function f() { return 333; } return f; } a()();
栈顶 a → window
开始调用,执行到return
发现需要调用f
继续加栈
栈顶 f → a → window
执行完f弹出f
继续执行a,执行完毕弹出a
最后全部执行完毕弹出window
算了文字解释太无力,直接上代码
var scope = "global scope"; // 一个全局变量 function checkscope() { var scope = "local scope"; // 定义一个局部变量 function f() { return scope; // 返回变量作用域中的scope的值 } return f(); // 返回这个函数 }
调用一下这个函数
checkscope(); "local scope"
接着这样执行
var scope = "global scope"; // 一个全局变量 function checkscope() { var scope = "local scope"; // 定义一个局部变量 function f() { return scope; // 返回变量作用域中的scope的值 } return f; // 返回这个函数 }
继续调用函数
checkscope()(); "local scope"
先看一个函数uniqueInteger()使用这个函数能够跟踪上次的返回值
var uniqueInteger = ( function() { var count = 0; return function() {return count++} }() );
这样子就使用闭包
uniqueInteger(); 0 uniqueInteger(); 1
每次返回是其上一次的值,并随便直接将值加1
至于为什么要这样写,如果不使用闭包,那么恶意代码就可以随便的将计数器重置了。。
uniqueInteger.count = 0; function uniqueInteger() { return uniqueInteger.count++; }
类似这样的,完全可以做到直接通过赋值,将其count的值重置。
而如果使用闭包,没有办法进行修改,为私有状态,也不会导致其一个页面内变量的冲突,或者是其覆盖。
var a = (function c(){ var a = 1; a++; console.log('已经执行'); return function b(){return a++}; }())
额,我大概解释一下这段代码。
首先呢,解释最外层的圆括号,因为如果没有圆括号,则这个是一个赋值语句,将一个匿名函数赋值给变量a,实际上是在内存中完成了栈中变量a指向匿名函数存储的空间的地址,如果有圆括号,实际上是告诉js解释器这是一个语句,需要js执行,消除了其function带来的影响。(ps;貌似火狐上不加也可以,也可以正常的运行)执行和引用的关系下方有。
然后呢,最后的圆括号,代表着其执行这个函数,因为js解析器将()解析为调用前方的函数名称,类似于运算符吧。但是实际上并不是运算符,因为能往其内传值,注意,这点是将其执行的结果保存在堆中,并完成其指向
其后,当直接输入a;,实际上执行并完成了一次调用,其返回值为函数b,将函数b完成一次引用,即变量a引用函数b,由于其存在引用关系,即栈中变量a保存的为其函数a的返回结果,(因为其不是不是对象,如果写a()()表示将函数a调用后返回的对象保存在栈中,然后将栈中的内容再次调用,由于是保存,并不存在其应用关系,所以执行完毕后直接垃圾回收)由于其保存的是函数b的作用域链,而函数b的作用域链是继承自函数a的作用域链,但是由于函数a的作用域链并没有引用导致其执行完后被垃圾回收(当不在有变量指向的时候)。所以呢,函数其值是在函数b中进行保存,如果修改函数c此时函数c并不会影响到函数b中的保存,因为其函数c的变量列表已被销毁,
最后,继续讨论起嵌套函数的引用,由于其父函数已被销毁,但是嵌套函数被引用,(注意:因为其父已经没有,所以是另开辟一块新的堆空间,用于存储其函数c的返回结果,注意是返回结果,而不是函数b)此时另外指定变量保存其结果,无论指定多少个变量保存其结果,都是新的空间的执行,没有任何的干扰,详细了解看下面,继续讨论
ps;如果是()()则代表其会被其垃圾回收
ps 还需要注意一点点的是由于其引用的是result的值,并不是其
最后,这样就能完成其变量保存在函数中,貌似叫做记忆?
所以呢,借助堆和栈就很好的能理解了闭包
function count() { var n = 0; return { count: function() { return n++; }, reset: function() { n = 0; } }; }
var c = count(); var d = count(); undefined
在分别执行一下下
c.count(); 0 d.count(); 0 c.count(); 1 d.count(); 1 c.reset(); undefined c.count(); 0 d.count(); 2
这一点体现了其互不影响性,表明其由于其父被回收,导致其子分别开创了一块在堆中新的内存空间,并完成其指向,互相不干扰。
其作用域链互不干扰
function count(n) { return { get count() { return n++; }, set count(m) { if ( m >= n) n = m; else throw new Error( '请输入一个正确的值' ); }, }; }
这个就不用解释啦,很简单啦
function test1() { val = value = 111; this.test = function() { return value - 1; }; this.test2 = function() { return value + 1; }; }
这同样是两个作用链域
不过这样写需要先执行其o.test1(),因为其方法在其函数内部,必须先执行一下,完成其方法的添加,否则会报错,
ee.test is not a function
提示找不到这个方法,
因为执行
ee.test1 = test1; function test1()
只是简单的进行赋值,并不能进行查看,所以导致其无法使用
所以嘛,要先执行一遍,让其方法添加进去
ee.test1(); undefined ee.test(); 110 ee.test2(); 112
这就是两个闭包,这两个闭包互相平行,同时继承于其父,但是又不受其父影响,很神奇吧,(@ο@)
叮 又发现一个莫名奇妙的东东 https://us.leancloud.cn 貌似目前水平能看懂一些了
this在父闭包显示的即为使用该方法的对象。
但是子就不一定了。
function test1() { val = value = 111; this.test = function() { return this.x - 1; }; this.test2 = function() { return this.x + 1; }; }
执行一下
ee.test(); 4443
这就尴尬了。
好吧。只能说是一般不这样用
一般这样写
var self = this;
将其值保存进一个self中
相关推荐:
以上是對js函數的實參,形參以及閉包的理解的詳細內容。更多資訊請關注PHP中文網其他相關文章!