JavaScript's implementation of scope (Scope) and context (Context) is a very unique feature of this language, partly due to its unique flexibility. Functions can accept different contexts and scopes. These concepts provide a solid foundation for many powerful design patterns in JavaScript. However, this concept can also easily cause confusion to developers. To this end, this article will comprehensively analyze these concepts and explain how different design patterns utilize them. Context and Scope
The first thing you need to know is that context and scope are two completely different concepts. Over the years, I've found that many developers (including myself) confuse these two concepts and mistakenly mix the two concepts. To be fair, many terms have been used confusingly over the years.
Each call to a function has a scope and context closely related to it. Fundamentally, scope is function-based, while context is object-based. In other words, scope involves variable access in the called function, and different calling scenarios are different. The context is always the value of the
this keyword, which is a reference to the object that owns (controls) the currently executing code. Variable scope
A variable can be defined in a local or global scope, which establishes different scopes for the variable's accessibility during runtime. Any global variable defined means that it needs to be declared outside the function body, survive the entire runtime, and can be accessed from any scope. Before ES6, local variables could only exist within the function body, and they had a different scope each time the function was called. Local variables can only be assigned, retrieved, and manipulated within the scope of the time they are called.
It should be noted that before ES6, JavaScript did not support block-level scope, which means that in
if statements, switch
statements, for
Block-level scope cannot be supported in loops and while
loops. In other words, JavaScript before ES6 cannot build block-level scope similar to that in Java (variables cannot be accessed outside the statement block). However, starting from ES6, you can define variables through the let
keyword, which corrects the shortcomings of the var
keyword, allows you to define variables like the Java language, and supports blocks level scope. Look at two examples: Before ES6, we used the
keyword to define variables: <pre class='brush:php;toolbar:false;'>function func() {
if (true) {
var tmp = 123;
}
console.log(tmp); // 123
}</pre>
can be accessed because the
key Variables declared with subscripts undergo a variable promotion process. In ES6 scenarios, it is recommended to use the let
keyword to define variables: <pre class='brush:php;toolbar:false;'>function func() {
if (true) {
let tmp = 123;
}
console.log(tmp); // ReferenceError: tmp is not defined
}</pre>
This method can avoid many errors.
What is
thisContext
Context usually depends on how the function is called. When a function is called as a method on an object,
this is set to the object that called the method: <pre class='brush:php;toolbar:false;'>var obj = {
foo: function(){
alert(this === obj);
}
};
obj.foo(); // true</pre>
This guideline also applies when calling a function using
operator to create an instance of an object. In this case, the value of this
inside the function's scope is set to the newly created instance: <pre class='brush:php;toolbar:false;'>function foo(){
alert(this);
}
new foo() // foo
foo() // window</pre>
When a bound function is called,
By default this is the global context, which in the browser points to the window
object. It should be noted that ES5 introduces the concept of strict mode. If strict mode is enabled, the context defaults to undefined
. Execution context (execution context)
JavaScript is a single-threaded language, which means that only one task can be executed at the same time. When the JavaScript interpreter initializes to execute code, it first defaults to the global execution context (execution context). From this point on, each call to the function will create a new execution context.
This often causes confusion for novices. A new term is mentioned here - execution context, which defines other data that variables or functions have access to, and determines their respective behaviors. It is more biased towards the role of scope rather than the Context we discussed earlier. Please be sure to carefully distinguish between the two concepts of execution environment and context (note: English can easily cause confusion). To be honest, this is a very bad naming convention, but it is set by the ECMAScript specification, so you might as well stick to it.
Each function has its own execution environment. When the execution flow enters a function, the function's environment is pushed into an environment stack (execution stack). After the function is executed, the stack pops its environment and returns control to the previous execution environment. The execution flow in ECMAScript programs is controlled by this convenient mechanism.
执行环境可以分为创建和执行两个阶段。在创建阶段,解析器首先会创建一个变量对象(variable object,也称为活动对象 activation object), 它由定义在执行环境中的变量、函数声明、和参数组成。在这个阶段,作用域链会被初始化,this
的值也会被最终确定。 在执行阶段,代码被解释执行。
每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。 需要知道,我们无法手动访问这个对象,只有解析器才能访问它。
作用域链(The Scope Chain)
当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。 作用域链包含了在环境栈中的每个执行环境对应的变量对象。通过作用域链,可以决定变量的访问和标识符的解析。 注意,全局执行环境的变量对象始终都是作用域链的最后一个对象。我们来看一个例子:
var color = "blue"; function changeColor(){ var anotherColor = "red"; function swapColors(){ var tempColor = anotherColor; anotherColor = color; color = tempColor; // 这里可以访问color, anotherColor, 和 tempColor } // 这里可以访问color 和 anotherColor,但是不能访问 tempColor swapColors(); } changeColor(); // 这里只能访问color console.log("Color is now " + color);
上述代码一共包括三个执行环境:全局环境、changeColor()的局部环境、swapColors()的局部环境。 上述程序的作用域链如下图所示:
从上图发现。内部环境可以通过作用域链访问所有的外部环境,但是外部环境不能访问内部环境中的任何变量和函数。 这些环境之间的联系是线性的、有次序的。
对于标识符解析(变量名或函数名搜索)是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始, 然后逐级地向后(全局执行环境)回溯,直到找到标识符为止。
闭包
闭包是指有权访问另一函数作用域中的变量的函数。换句话说,在函数内定义一个嵌套的函数时,就构成了一个闭包, 它允许嵌套函数访问外层函数的变量。通过返回嵌套函数,允许你维护对外部函数中局部变量、参数、和内函数声明的访问。 这种封装允许你在外部作用域中隐藏和保护执行环境,并且暴露公共接口,进而通过公共接口执行进一步的操作。可以看个简单的例子:
function foo(){ var localVariable = 'private variable'; return function bar(){ return localVariable; } } var getLocalVariable = foo(); getLocalVariable() // private variable
模块模式最流行的闭包类型之一,它允许你模拟公共的、私有的、和特权成员:
var Module = (function(){ var privateProperty = 'foo'; function privateMethod(args){ // do something } return { publicProperty: '', publicMethod: function(args){ // do something }, privilegedMethod: function(args){ return privateMethod(args); } }; })();
模块类似于一个单例对象。由于在上面的代码中我们利用了(function() { ... })();
的匿名函数形式,因此当编译器解析它的时候会立即执行。 在闭包的执行上下文的外部唯一可以访问的对象是位于返回对象中的公共方法和属性。然而,因为执行上下文被保存的缘故, 所有的私有属性和方法将一直存在于应用的整个生命周期,这意味着我们只有通过公共方法才可以与它们交互。
另一种类型的闭包被称为立即执行的函数表达式(IIFE)。其实它很简单,只不过是一个在全局环境中自执行的匿名函数而已:
(function(window){ var foo, bar; function private(){ // do something } window.Module = { public: function(){ // do something } }; })(this);
对于保护全局命名空间免受变量污染而言,这种表达式非常有用,它通过构建函数作用域的形式将变量与全局命名空间隔离, 并通过闭包的形式让它们存在于整个运行时(runtime)。在很多的应用和框架中,这种封装源代码的方式用处非常的流行, 通常都是通过暴露一个单一的全局接口的方式与外部进行交互。
Call和Apply
这两个方法内建在所有的函数中(它们是Function
对象的原型方法),允许你在自定义上下文中执行函数。 不同点在于,call
函数需要参数列表,而apply
函数需要你提供一个参数数组。如下:
var o = {}; function f(a, b) { return a + b; } // 将函数f作为o的方法,实际上就是重新设置函数f的上下文 f.call(o, 1, 2); // 3 f.apply(o, [1, 2]); // 3
两个结果是相同的,函数f
在对象o
的上下文中被调用,并提供了两个相同的参数1
和2
。
在ES5中引入了Function.prototype.bind
方法,用于控制函数的执行上下文,它会返回一个新的函数, 并且这个新函数会被永久的绑定到bind
方法的第一个参数所指定的对象上,无论该函数被如何使用。 它通过闭包将函数引导到正确的上下文中。对于低版本浏览器,我们可以简单的对它进行实现如下(polyfill):
if(!('bind' in Function.prototype)){ Function.prototype.bind = function(){ var fn = this, context = arguments[0], args = Array.prototype.slice.call(arguments, 1); return function(){ return fn.apply(context, args.concat(arguments)); } } }
bind()
方法通常被用在上下文丢失的场景下,例如面向对象和事件处理。之所以要这么做, 是因为节点的addEventListener
方法总是为事件处理器所绑定的节点的上下文中执行回调函数, 这就是它应该表现的那样。但是,如果你想要使用高级的面向对象技术,或需要你的回调函数成为某个方法的实例, 你将需要手动调整上下文。这就是bind
方法所带来的便利之处:
function MyClass(){ this.element = document.createElement('p'); this.element.addEventListener('click', this.onClick.bind(this), false); } MyClass.prototype.onClick = function(e){ // do something };
回顾上面bind
方法的源代码,你可能会注意到有两次调用涉及到了Array
的slice
方法:
Array.prototype.slice.call(arguments, 1); [].slice.call(arguments);
我们知道,arguments
对象并不是一个真正的数组,而是一个类数组对象,虽然具有length属性,并且值也能够被索引, 但是它们不支持原生的数组方法,例如slice
和push
。但是,由于它们具有和数组类似的行为,数组的方法能够被调用和劫持, 因此我们可以通过类似于上面代码的方式达到这个目的,其核心是利用call
方法。
这种调用其他对象方法的技术也可以被应用到面向对象中,我们可以在JavaScript中模拟经典的继承方式:
MyClass.prototype.init = function(){ // call the superclass init method in the context of the "MyClass" instance MySuperClass.prototype.init.apply(this, arguments); }
也就是利用call
或apply
在子类(MyClass
)的实例中调用超类(MySuperClass
)的方法。
ES6中的箭头函数
ES6中的箭头函数可以作为Function.prototype.bind()
的替代品。和普通函数不同,箭头函数没有它自己的this
值, 它的this
值继承自外围作用域。
对于普通函数而言,它总会自动接收一个this
值,this
的指向取决于它调用的方式。我们来看一个例子:
var obj = { // ... addAll: function (pieces) { var self = this; _.each(pieces, function (piece) { self.add(piece); }); }, // ... }
在上面的例子中,最直接的想法是直接使用this.add(piece)
,但不幸的是,在JavaScript中你不能这么做, 因为each
的回调函数并未从外层继承this
值。在该回调函数中,this
的值为window
或undefined
, 因此,我们使用临时变量self
来将外部的this
值导入内部。我们还有两种方法解决这个问题:
使用ES5中的bind()方法
var obj = { // ... addAll: function (pieces) { _.each(pieces, function (piece) { this.add(piece); }.bind(this)); }, // ... }
使用ES6中的箭头函数
var obj = { // ... addAll: function (pieces) { _.each(pieces, piece => this.add(piece)); }, // ... }
在ES6版本中,addAll
方法从它的调用者处获得了this
值,内部函数是一个箭头函数,所以它集成了外部作用域的this
值。
注意:对回调函数而言,在浏览器中,回调函数中的this
为window
或undefined
(严格模式),而在Node.js中, 回调函数的this
为global
。实例代码如下:
function hello(a, callback) { callback(a); } hello('weiwei', function(a) { console.log(this === global); // true console.log(a); // weiwei });
小结
在你学习高级的设计模式之前,理解这些概念非常的重要,因为作用域和上下文在现代JavaScript中扮演着的最基本的角色。 无论我们谈论的是闭包、面向对象、继承、或者是各种原生实现,上下文和作用域都在其中扮演着至关重要的角色。 如果你的目标是精通JavaScript语言,并且深入的理解它的各个组成,那么作用域和上下文便是你的起点。
The above is the detailed content of Deep understanding of scope and context in JavaScript (picture). For more information, please follow other related articles on the PHP Chinese website!

C and JavaScript achieve interoperability through WebAssembly. 1) C code is compiled into WebAssembly module and introduced into JavaScript environment to enhance computing power. 2) In game development, C handles physics engines and graphics rendering, and JavaScript is responsible for game logic and user interface.

JavaScript is widely used in websites, mobile applications, desktop applications and server-side programming. 1) In website development, JavaScript operates DOM together with HTML and CSS to achieve dynamic effects and supports frameworks such as jQuery and React. 2) Through ReactNative and Ionic, JavaScript is used to develop cross-platform mobile applications. 3) The Electron framework enables JavaScript to build desktop applications. 4) Node.js allows JavaScript to run on the server side and supports high concurrent requests.

Python is more suitable for data science and automation, while JavaScript is more suitable for front-end and full-stack development. 1. Python performs well in data science and machine learning, using libraries such as NumPy and Pandas for data processing and modeling. 2. Python is concise and efficient in automation and scripting. 3. JavaScript is indispensable in front-end development and is used to build dynamic web pages and single-page applications. 4. JavaScript plays a role in back-end development through Node.js and supports full-stack development.

C and C play a vital role in the JavaScript engine, mainly used to implement interpreters and JIT compilers. 1) C is used to parse JavaScript source code and generate an abstract syntax tree. 2) C is responsible for generating and executing bytecode. 3) C implements the JIT compiler, optimizes and compiles hot-spot code at runtime, and significantly improves the execution efficiency of JavaScript.

JavaScript's application in the real world includes front-end and back-end development. 1) Display front-end applications by building a TODO list application, involving DOM operations and event processing. 2) Build RESTfulAPI through Node.js and Express to demonstrate back-end applications.

The main uses of JavaScript in web development include client interaction, form verification and asynchronous communication. 1) Dynamic content update and user interaction through DOM operations; 2) Client verification is carried out before the user submits data to improve the user experience; 3) Refreshless communication with the server is achieved through AJAX technology.

Understanding how JavaScript engine works internally is important to developers because it helps write more efficient code and understand performance bottlenecks and optimization strategies. 1) The engine's workflow includes three stages: parsing, compiling and execution; 2) During the execution process, the engine will perform dynamic optimization, such as inline cache and hidden classes; 3) Best practices include avoiding global variables, optimizing loops, using const and lets, and avoiding excessive use of closures.

Python is more suitable for beginners, with a smooth learning curve and concise syntax; JavaScript is suitable for front-end development, with a steep learning curve and flexible syntax. 1. Python syntax is intuitive and suitable for data science and back-end development. 2. JavaScript is flexible and widely used in front-end and server-side programming.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

DVWA
Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

MantisBT
Mantis is an easy-to-deploy web-based defect tracking tool designed to aid in product defect tracking. It requires PHP, MySQL and a web server. Check out our demo and hosting services.

SublimeText3 Chinese version
Chinese version, very easy to use

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),