ホームページ > ウェブフロントエンド > jsチュートリアル > JavaScriptの関数とクロージャの話_基礎知識

JavaScriptの関数とクロージャの話_基礎知識

WBOY
リリース: 2016-05-16 17:37:09
オリジナル
886 人が閲覧しました

闭包这东西,说难也难,说不难也不难,下面我就以自己的理解来说一下闭包

一、闭包的解释说明

对于函数式语言来说,函数可以保存内部的数据状态。对于像C#这种编译型命令式语言来说,由于代码总是在代码段中执行,而代码段是只读的,因此函数中的数据只能是静态数据。函数内部的局部变量存放在栈上,在函数执行结束以后,所占用的栈被释放,因此局部变量是不能保存的。

Javascript采用词法作用域,函数的执行依赖于变量作用域,这个作用域是在定义函数时确定的。因此Javascript中函数对象不仅保存代码逻辑,还必须引用当前的作用域链。Javascript中函数内部的局部变量可以被修改,而且当再次进入到函数内部的时候,上次被修改的状态仍然持续。这是因为因为局部变量并不保存在栈上,而是通过一个对象来保存。

决定使用哪个变量是由作用域链决定的,每次生成函数实例时,都会为之创建一个对象用来保存局部变量,并且把这个用于保存局部变量的对象加入作用域链中。不同函数对象可以通过作用域链关联起来。Javascript中所有函数都是闭包,我们不能避免“产生”闭包。

引用一张《Javascript高级程序设计》中的图来说明,虽然这张图并不完全说明所有情况。图中的activation object就是用于保存变量的对象。

 


简而言之,在Javascript中:

闭包:函数实例保存着在执行时所需要的变量的引用,而不会复制保存当时变量的值。(在Object C的实现中,我们可以选择保存当时的值或者是引用)

作用域链:解析变量时查找变量所在的方式,以var作为终止符号,如果链上一直没有var,则一直追溯到全局对象为止。

C#中的闭包特性是由编译器把局部变量转换成引用类型的对象成员实现的。

二、闭包的使用
 
下面通过一些具体例子来说明如何利用闭包这一特性:

1.闭包是在定义的时候产生的

function Foo(){ function A(){} function B(){} function C(){}}
我们每次执行Foo()的时候,都有有A,B,C这三个函数实例(闭包)产生,当Foo执行完毕,生成的实例没有其他引用,因此会被当成垃圾随之销毁(不一定是马上销毁)。
我们来证实一下作用域链是在函数定义时确定的,所以这里显示的应该是'local scope'

var scope = "global scope"; function checkscope() { var scope = "local scope"; function f() { return scope; } return f;}checkscope()()


同样道理:

(function(){ function A(){} function B(){} function C(){}}())
上面的表达式执行完后也会有A,B,C这三个函数实例(闭包)产生,因为这是一个立即执行的匿名函数,这三个闭包只能产生一次。生成的闭包没有其他引用,因此会被当成垃圾随之销毁(不一定是马上销毁)。

我们之所以这么写,目地有两个

1.避免污染全局对象

2.避免多次产生相同的函数实例

 

对比下面两个例子,闭包是如何保存作用域链的:

 function A(){} //比较省内存的写法,创建对象速度快,开销小 (function(prototype){ var name = "a"; function sayName () { alert(name); } function ChangeName() { name += "_changed" } prototype.sayName = sayName;//引用通过执行匿名函数产生的闭包,闭包只会产生一次 prototype.changeName = ChangeName; }(A.prototype)) var a1 = new A(); var a2 = new A();
 a1.sayName(); a1.changeName(); a2.sayName();


--------------------------------------------------------------------------------

function B(){ //プロトタイプ チェーンは比較的短く、メソッドはすぐに見つかりますが、new がコンストラクターを呼び出すたびに、2 つの関数インスタンスと 1 つの変数が生成され、より多くのメモリが消費されます。 var name = "b"; functionsayName() {alert(name); } function changeName() { name = "_changed" } this.sayName =sayName;//関数 B が呼び出されるたびに、新しいクロージャが追加されます。 close of this.changeName = changeName; }//関数呼び出しの前に new キーワードがある場合、関数はコンストラクターとして使用されます。 //基本的に、コンストラクターとして呼び出すことと通常の関数として呼び出すことに違いはありません。 B() が直接呼び出された場合、このオブジェクトはグローバル オブジェクトにバインドされ、新しく生成されたクロージャが古いクロージャを置き換えて、グローバル オブジェクトのchangeNameプロパティとsayNameプロパティに割り当てられるため、古いクロージャが処理されます。ガベージコレクションとして。 //コンストラクターとして使用される場合、new キーワードは新しいオブジェクト (this はこの新しいオブジェクトを指します) を生成し、この新しいオブジェクトのsayName プロパティとchangeName プロパティを初期化するため、生成された各クロージャは参照のために保持されます。 var b1 = 新しい b1.sayName(); b1.sayName();


3. リーク問題: コンパイル言語では、関数本体は常にファイルのコード セグメント内にあり、実行時に実行可能としてマークされたメモリ領域にロードされます。実際、関数自体にライフサイクルがあるとは考えていません。ほとんどの場合、ポインターやオブジェクトなどの「参照型データ構造」にはライフサイクルやリークの問題があると考えられます。

JavaScript におけるメモリ リークの本質は、ローカル変数を保持する関数を定義するときに生成されたオブジェクトが、参照が存在するためにガベージとして扱われずに収集されることです。

1. 循環参照があります

2. DOM 内の IE6 のメモリ リークなど、一部のオブジェクトは決して破棄できません。または、破棄されたときに Javascript エンジンに通知できないため、一部の JavaScript クロージャーは破棄できません。このような状況は通常、JavaScript ホスト オブジェクトと Javascript のネイティブ オブジェクト間の通信ミスが原因で発生します。


ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート