|
スコープは、エンジンが現在のスコープおよびネストされたスコープ内の識別子に基づいて変数をクエリする方法を定義する一連のルールを定義します。一方、N 個のスコープで構成されるスコープ チェーンは、関数スコープ内の識別子によって検出される値を決定します。
したがって、これを次のように要約できます: スコープは、現在のコンテキストで定義された変数の可視性を決定します。つまり、下位レベルのスコープはそれにアクセスできます。また、スコープ チェーン (スコープ チェーン) は、現在のコンテキストで識別子の値を検索する方法も決定します。
スコープはレキシカルスコープとダイナミックスコープに分かれています。字句スコープは文字通り字句段階で定義されたスコープです。つまり、スコープは、レクサーがソース コードを処理するときに、ソース コード内の変数とブロックの位置に基づいて設定されます。 JavaScript は字句スコープを使用します。
変数のアクセス規則:
let a = 1
function foo () {
let b = 1 + a
let c = 2
console.log(b) // 2
}
console.log(c) // error 全局作用无法访问到 c
foo()
let a = 1
let b = 2
function foo () {
let b = 3
function too () {
console.log(a) // 1
console.log(b) // 3
}
too()
}
foo()
もう一度言いますが、JavaScript スコープは変数のアクセス可能な範囲を厳密に制限します。つまり、場所に基づいて、ソース コード内のコードとブロックの場合、ネストされたスコープはネストされたスコープにアクセスできます。 (このルールは、ファーム全体にルールがあり、逆方向に餌を与えることができないことを示しています。)
スコープチェーン
スコープチェーンは、現在の環境と上位環境の一連のスコープで構成されており、秩序が保たれています。現在の実行環境のアクセス許可に準拠した変数および関数へのアクセス。
上記の説明は少しわかりにくいですが、私のような頭の弱い人にとっては、理解するために何度か頭の中で「読む」必要があります。では、スコープチェーンは何のためにあるのでしょうか? 簡単に言うと、スコープ チェーンは識別子を解析し、式の実行時にその式が依存する変数の値を返す役割を果たします。より簡単な答え: スコープ チェーンは、変数を検索するために使用されます。スコープ チェーンは、直列に接続された一連のスコープです。
関数の実行中、変数が見つかるたびに、識別子の解決プロセスが実行され、データを取得して保存する場所が決定されます。このプロセスは、現在実行中の関数のスコープであるスコープ チェーンの先頭から開始され (下図の左から右へ)、同じ名前の識別子が見つかった場合は、その識別子に対応する値を検索します。見つからない場合は続行します。スコープ チェーン内の次のスコープが検索されます。スコープが見つからない場合、識別子は未定義とみなされます。関数の実行中、解析する価値のある各識別子はこの検索プロセスを通過する必要があります。
問題を具体的に分析するには、スコープチェーンが配列 (Scope Array) であり、配列メンバーが一連の変数オブジェクトで構成されていると仮定できます。配列の一方向チャネルを使用できます。つまり、上の図は、変数オブジェクト内の識別子を左から右にクエリすることをシミュレートし、上部スコープ内の変数にアクセスできるようにします。最上位レベル (グローバル スコープ) まで検索し、見つかると検索は停止します。したがって、内部変数は同じ名前の外部変数をシールドできます。考えてみてください。変数が内側から外側に検索されない場合、言語設計全体が非常に複雑になります (雛鶏が餌を見つけるために複雑なルールのセットを設計する必要があります)
まだ上の栗:
let a = 1
let b = 2
function foo () {
let b = 3
function too () {
console.log(a) // 1
console.log(b) // 3
}
too()
}
foo()
スコープ入れ子構造は次のようになります:
栗子中,当 javascript 引擎执行到函数 too 时, 全局、函数 foo、函数 too 的上下文分别会被创建。上下文内包含它们各自的变量对象和作用域链(注意: 作用域链包含可访问到的上层作用域的变量对象,在上下文创建阶段根据作用域规则被收集起来形成一个可访问链),我们设定他们的变量对象分别为VO(global),VO(foo), VO(too)。而 too 的作用域链,则同时包含了这三个变量对象,所以 too 的执行上下文可如下表示:
too = {
VO: {...}, // 变量对象
scopeChain: [VO(too), VO(foo), VO(global)], // 作用域链
}
我们可以直接用scopeChain
来表示作用域链数组,数组的第一项scopeChain[0]为作用域链的最前端(当前函数的变量对象),而数组的最后一项,为作用域链的最末端(全局变量对象 window )。所有作用域链的最末端都为全局变量对象。
再举个栗子:
let a = 1
function foo() {
console.log(a)
}
function too() {
let a = 2
foo()
}
too() // 1
这个栗子如果对作用域的特点理解不透彻很容易以为输出是2。但其实最终输出的是 1。 foo() 在执行的时候先在当前作用域内查找变量 a 。然后根据函数定义时的作用域关系会在当前作用域的上层作用域里查找变量标识符 a,所以最后查到的是全局作用域的 a 而不是 foo函数里面的 a 。
变量对象、执行上下文会在后面介绍。
闭包
在 JavaScript 中,函数和函数声明时的词法作用域形成闭包。我们来看个闭包的例子
let a = 1
function foo() {
let a = 2
function too() {
console.log(a)
}
return too
}
foo()() // 2
这是一个闭包的栗子,一个函数执行后返回另一个可执行函数,被返回的函数保留有对它定义时外层函数作用域的访问权。foo()()
调用时依次执行了 foo、too 函数。too 虽然是在全局作用域里执行的,但是too定义在 foo 作用域里面,根据作用域链规则取最近的嵌套作用域的属性 a = 2。
再拿农场的故事做比如。农场主发现还有一种方法会更节约成本,就是让每个鸡妈妈作为家庭成员的‘饲养员’, 从而改变了之前的‘饲养结构’。
关于闭包会在后面的章节里也会有介绍。
从作用域链的结构可以发现,javascript
引擎在查找变量标识符时是依据作用域链依次向上查找的。当标识符所在的作用域位于作用域链的更深的位置,读写的时候相对就慢一些。所以在编写代码的时候应尽量少使用全局代码,尽可能的将全局的变量缓存在局部作用域中。
不加强记忆很容记错作用域与后面将要介绍的执行上下文的区别。代码的执行过程分为编译阶段和解释执行阶段。始终应该记住javascript
作用域在源代码的编码阶段就确定了,而作用域链是在编译阶段被收集到执行上下文的变量对象里的。所以作用域、作用域链都是在当前运行环境内代码执行前就确定了。这里暂且不过多的展开执行上下文的概念,可以关注后续文章。
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
PromiseA+的实现步骤详解
EasyCanvas绘图库在Pixeler项目开发中使用实战总结