私は長い間JavaScriptを勉強しています
今日はスコープの知識を思い出そうと思います
スコープの知識はとても基礎的でとても重要です
ここでJavaScriptのスコープとスコープチェーンに関する関連知識をまとめます
スコープとは何ですか?
スコープとは、変数が参照でき、関数が有効になる領域です
メモリ空間内の値を取得および変更する能力を制限します
スコープはすべての言語に存在します
スコープはjsエンジンとして理解できます名前による検索 変数の一連のルール
スコープを理解することによってのみ、クロージャなどの一連の問題を理解することができます
関数が特別な実行可能オブジェクトであることは誰もが知っています
関数はオブジェクトであるため、次のことができます属性の所有
関数にはこの内部属性 [[Scope]] があります (これは使用できません。JS エンジンによって使用されます)
関数が作成されると、この内部属性にはスコープ内のオブジェクトのコレクションが含まれます。関数が作成されます
このコレクションはチェーンでリンクされており、関数のスコープ チェーンと呼ばれます
スコープ チェーン上の各オブジェクトは変数オブジェクト (Variable Object) と呼ばれます
すべての変数オブジェクトはキーの形式で存在します値ペア
例を挙げて、以下のグローバル関数を見てください
var a = 1;function foo(){ ...}
foo 関数が作成されると、グローバル オブジェクト GO (Global Object) がそのスコープ チェーンに挿入されます。これには、グローバルに定義されたすべての変数が含まれます
// 伪代码 foo.[[Scope]] = { GO: { this: window , window: ... , document: ... , ...... a: undefined, // 预编译阶段还不知道a值是多少 foo: function(){...}, } }
関数が実行されると、実行環境/実行コンテキストと呼ばれる内部オブジェクトが作成されます
関数が実行されるときの環境を定義します
実行環境は関数が実行されるたびに一意です
実行環境は複数回作成されます関数を複数回呼び出すことによって
そして関数が実行されます 完了後、実行環境は破棄されます
実行環境には識別子を解析するための独自のスコープチェーンがあります
これを見た後は少し混乱するかもしれません、私の理解を説明させてくださいあなたへ
[[Scope]] と実行コンテキストはスコープチェーンを保存しますが、同じものではありません
ここで違いを明確にしましょう
[[Scope]] 属性は関数の作成時に生成され、常に存在します
関数が実行されると実行コンテキストが生成されます
上記の例を展開して詳しく説明します
var a = 1;function foo(x, y){ var b = 2; function bar(){ var c = 3; } bar(); } foo(100, 200);
次に、スコープチェーンと実行環境について説明します。これらのコード行を通して詳細を説明します
私はまだすべての学生に最初に読むことをお勧めします 私が書いたプリコンパイルを見てください
まず第一に、実行フローの流れでは、関数 foo() がグローバル環境で作成されます (作成された)プリコンパイル段階)なので、foo 関数は属性 [[Scope]]
// 伪代码:foo函数创建产生[[Scope]]对象 foo.[[Scope]] = { GO: { this: window , window: ... , document: ... , a: undefined, //预编译阶段还不知道a的值是多少,执行过程中会修改 foo: function(){...}, ...... } }
を持ちます。 foo 関数が実行される前に、実行コンテキストが作成されます (実行コンテキストを当面は EC と書きます)実行コンテキストは foo の内部 [[Scope]] 属性を取得して保存し、プリコンパイルによってアクティブ オブジェクト AO (Active.オブジェクト) foo 関数の実行前に、このオブジェクトは EC スコープ チェーンのフロントエンドにプッシュされます
// 伪代码:foo函数执行前产生执行期上下文EC复制foo中[[Scope]]属性保存的作用域链 foo.EC = { GO: { this: window , window: ... , document: ... , a: 1, foo: function(){...}, ...... } }
// 伪代码:foo函数预编译产生AO活动对象,挂载到foo中EC作用域链的最前端 foo.EC = { AO: { this: window, arguments: [100,200], x: 100, y: 200, b: undefined, bar: function(){...} }, GO: { this: window , window: ... , document: ... , a: 1, foo: function(){...}, ...... } }
foo 関数はプリコンパイル段階にあります bar 関数が作成されるため、bar 関数これは、bar が作成されるスコープ内のオブジェクトのコレクションを含む属性 [[Scope]] を作成します。つまり、foo.EC
// 伪代码:bar函数创建产生[[Scope]]对象 bar.[[Scope]] = { AO: { this: window, arguments: [100,200], x: 100, y: 200, b: undefined, bar: function(){...} }, GO: { this: window , window: ... , document: ... , a: 1, foo: function(){...}, ...... } }
bar 関数が実行され、プロセスは foo と同じです。関数の実行も同様なので、最終結果を直接書きます
// 伪代码:bar函数执行产生执行上下文 bar.EC = { AO: { this: window, arguments: [], c: undefined, }, AO: { this: window, arguments: [100,200], x: 100, y: 200, b: 2, bar: function(){...} }, GO: { this: window , window: ... , document: ... , a: 1, foo: function(){...}, ...... } }
bar関数が実行され、実行環境が破棄されます。これは、delete bar.EC
と同等ですdelete bar.EC
foo函数执行完毕,执行环境被销毁,相当于delete foo.EC
程序结束
不知道我写这么多大家理解没有
js引擎就是通过作用域链的规则来进行变量查找(准确的说应该是执行上下文的作用域链)
查找过程就拿上面的代码来说,比如说我在bar函数内加一行console.log(a);
foo関数が実行され、実行環境が破壊されます。これはdelete foo.EC
に相当します
console.log(a);< という行を bar 関数 /code> に追加します 🎜 その後、bar 関数が実行されると、JS エンジンは を出力する必要があるため、スコープ チェーンに移動して検索します。 🎜 最初の層には AO はありません 🎜 2 番目の層には AO はありません 🎜 変数 a は 3 番目の層で見つかります GO 🎜 それで変数 a の値を返します🎜 誰もがスコープを理解した後は、グローバル環境がローカル環境にアクセスできない理由を理解します🎜性能问题
今天写high了,像吃了炫迈一样,那就顺便把性能问题也说清楚了吧
js引擎查找作用域链是为了解析标识符
占用了时间理所当然的产生了性能开销
所以解析标识符有代价,你的变量在执行环境作用域链的位置越深,读写速度就越慢
这很容易理解
在函数中读写局部变量总是最快的,读写全局变量通常最慢
当然了,这些额外的性能开销对于优化js引擎(比如chrome的V8 (⊙▽⊙))来说可能微不足道,甚至可以毫不夸张的说没有性能损失
但是还是要照顾大多浏览器
所以推荐大家养成这样的编码习惯:尽量使用局部变量(缓存)
我举一个小例子
function demo(){
var a = document.getElementById('a');
var b = document.getElementById('b');
var c = document.getElementById('c');
a.onclick = function(){ ...
}
a.style.left = ...;
a.style.top = ...;
b.style.backgroundColor = ...;
c.className = ...;
document.body.appendChild(...);
document.body.appendChild(...);
document.body.appendChild(...);
}
ログイン後にコピー这段代码看起来缓存了,优化了代码
但其实这段代码执行过程中,js引擎解析标识符,要查找6次document
而且document存在于window对象
也就是作用域链的最末尾
所以我们再进行缓存,包括document.body、a.style
再加上单一var原则
重构函数
function demo(){
var doc = document,
bd = doc.body,
a = doc.getElementById('a'),
b = doc.getElementById('b'),
styleA = a.style;
a.onclick = function(){ ...
}
styleA.left = ...;
styleA.top = ...;
styleA.backgroundColor = ...;
b.className = ...;
bd.appendChild(...);
bd.appendChild(...);
bd.appendChild(...);
}
ログイン後にコピー总结
其实写了这么多,还有一个问题我没写到,就是作用域链在某些特殊情况下是可以动态改变的
比如with()、eval()等等,当然这些都不建议使用,我总结了一篇文章
有兴趣的同学可以看看 ->传送门<-
还是总结一下今天写的作用域链相关知识
作用域是变量能够引用、函数能够生效的区域
函数创建时,产生内部属性[[Scope]]包含函数被创建的作用域中对象的集合(作用域链)
作用域链上每个对象称为可变对象(Variable Obejct),
每一个可变对象都以键值对形式存在(VO要细分的话,全局对象GO和活动对象AO)
函数执行时,创建内部对象叫做执行环境/执行上下文(execution context)
它定义了一个函数执行时的环境,函数每次执行时的执行环境独一无二
函数执行结束便会销毁
js引擎就通过函数执行上下文的作用域链规则来进行解析标识符(用于读写),从作用域链顶端依次向下查找
尽量缓存局部变量,减少作用域查找性能开销(照顾未优化浏览器)
以上就是JavaScript内部属性[[Scope]]与作用域链及其性能问题的内容,更多相关内容请关注PHP中文网(www.php.cn)!