New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

深入理解JS中声明提升、作用域(链)和`this`关键字 #16

Open
creeperyang opened this Issue Jan 21, 2016 · 22 comments

Comments

Projects
None yet
@creeperyang
Owner

creeperyang commented Jan 21, 2016

这个issue试图阐述JavaScript这门语言的3个难点:声明提升作用域(链)this

首先推荐https://github.com/getify/You-Dont-Know-JS,这是一本非常棒的JavaScript书籍,几乎所有的JS知识点都包括并且详细解释了。看一遍相信必有大收获。

1. 声明提升

大部分编程语言都是先声明变量再使用,但在JS中,事情有些不一样:

console.log(a); // undefined
var a = 1;

上面是合法的JS代码,正常输出undefined而不是报错Uncaught ReferenceError: a is not defined。为什么?就是因为声明提升(hoisting)。

1.1 变量声明

参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var

语法:

var varname1 [= value1 [, varname2 [, varname3 ... [, varnameN]]]];

变量名可以是任意合法标识符;值可以是任意合法表达式。

重点:

  • 变量声明,不管在哪里发生(声明),都会在任意代码执行前处理。(Variable declarations, wherever they occur, are processed before any code is executed. )。
  • var声明的变量的作用域就是当前执行上下文(execution context),即某个函数,或者全局作用域(声明在函数外)。
  • 赋值给未声明的变量,当执行时会隐式创建全局变量(成为global的属性)。

声明变量和未声明变量的区别

  • 声明变量通常是局部的,未声明变量通常全局的。
  • 声明变量在任意代码执行前创建,未声明变量直到赋值时才存在。
  • 声明变量是execution context(function/global)的non-configurable 属性,未声明变量则是configurable。

es5 strict mode,赋值给未声明的变量将报错。

1.2 定义函数(Defining functions

定义一个函数有两种方式:函数声明(function definition/declaration/statement)和函数表达式( function expression)。

1.2.1 function definition

语法:function name(arguments) {}

对参数而言,primitive parameter是传值,对象是传引用。

1.2.2 function expression

语法:var fun = function (arguments) {}

函数表达式中函数可以不需要名字,即匿名函数。

1.2.3 其它

还可以用 Function构造函数来创建函数。

函数内部引用函数本身有3种方式。比如var foo = function bar(){};

  • 函数名字,即bar()
  • arguments.callee()
  • foo()

1.3 声明提升

1.1提到,var 声明的变量会在任意代码执行前处理,这意味着在任意地方声明变量都等同于在顶部声明——即声明提升。1.2特意强调了函数定义,因为声明提升中,需要综合考虑一般变量和函数。

在JavaScript中,一个变量名进入作用域的方式有 4 种:

  1. Language-defined:所有的作用域默认都会给出 thisarguments 两个变量名(global没有arguments);
  2. Formal parameters(形参):函数有形参,形参会添加到函数的作用域中;
  3. Function declarations(函数声明):如 function foo() {};
  4. Variable declarations(变量声明):如 var foo,包括_函数表达式_。

函数声明和变量声明总是会被移动(即hoist)到它们所在的作用域的顶部(这对你是透明的)。

而变量的解析顺序(优先级),与变量进入作用域的4种方式的顺序一致。

一个详细的例子:

function testOrder(arg) {
    console.log(arg); // arg是形参,不会被重新定义
    console.log(a); // 因为函数声明比变量声明优先级高,所以这里a是函数
    var arg = 'hello'; // var arg;变量声明被忽略, arg = 'hello'被执行
    var a = 10; // var a;被忽视; a = 10被执行,a变成number
    function a() {
        console.log('fun');
    } // 被提升到作用域顶部
    console.log(a); // 输出10
    console.log(arg); // 输出hello
}; 
testOrder('hi');
/* 输出:
hi 
function a() {
        console.log('fun');
    }
10 
hello 
*/
@creeperyang

This comment has been minimized.

Show comment
Hide comment
@creeperyang

creeperyang Feb 19, 2016

Owner

2. this

this关键词是JavaScript中最令人疑惑的机制之一。this是非常特殊的关键词标识符,在每个函数的作用域中被自动创建,但它到底指向什么(对象),很多人弄不清。

当函数被调用,一个activation record(即 execution context)被创建。这个record包涵信息:函数在哪调用(call-stack),函数怎么调用的,参数等等。record的一个属性就是this,指向函数执行期间的this对象。

  • this不是author-time binding,而是 runtime binding。
  • this的上下文基于函数调用的情况。和函数在哪定义无关,而和函数怎么调用有关。

2.1 this在具体情况下的分析

2.1.1 Global context

在全局上下文(任何函数以外),this指向全局对象。

console.log(this === window); // true
2.1.2 Function context

在函数内部时,this由函数怎么调用来确定。

2.1.2.1 Simple call

简单调用,即独立函数调用。由于this没有通过call来指定,且this必须指向对象,那么默认就指向全局对象。

function f1(){
  return this;
}

f1() === window; // global object

在严格模式下,this保持进入execution context时被设置的值。如果没有设置,那么默认是undefined。它可以被设置为任意值**(包括null/undefined/1等等基础值,不会被转换成对象)**。

function f2(){
  "use strict"; // see strict mode
  return this;
}

f2() === undefined;
2.1.2.2 Arrow functions

在箭头函数中,this由词法/静态作用域设置(set lexically)。它被设置为包含它的execution context的this,并且不再被调用方式影响(call/apply/bind)。

var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true

// Call as a method of a object
var obj = {foo: foo};
console.log(obj.foo() === globalObject); // true

// Attempt to set this using call
console.log(foo.call(obj) === globalObject); // true

// Attempt to set this using bind
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
2.1.2.3 As an object method

当函数作为对象方法调用时,this指向该对象。

var o = {
  prop: 37,
  f: function() {
    return this.prop;
  }
};

console.log(o.f()); // logs 37

this on the object's prototype chain

原型链上的方法根对象方法一样,作为对象方法调用时this指向该对象。

2.1.2.4 构造函数

在构造函数(函数用new调用)中,this指向要被constructed的新对象。

2.1.2.5 call和apply

Function.prototype上的callapply可以指定函数运行时的this

function add(c, d){
  return this.a + this.b + c + d;
}

var o = {a:1, b:3};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

注意,当用callapply而传进去作为this的不是对象时,将会调用内置的ToObject操作转换成对象。所以4将会装换成new Number(4)null/undefined由于无法转换成对象,全局对象将作为this

2.1.2.6 bind

ES5引进了Function.prototype.bindf.bind(someObject)会创建新的函数(函数体和作用域与原函数一致),但this被永久绑定到someObject,不论你怎么调用。

2.1.2.7 As a DOM event handler

this自动设置为触发事件的dom元素。

Owner

creeperyang commented Feb 19, 2016

2. this

this关键词是JavaScript中最令人疑惑的机制之一。this是非常特殊的关键词标识符,在每个函数的作用域中被自动创建,但它到底指向什么(对象),很多人弄不清。

当函数被调用,一个activation record(即 execution context)被创建。这个record包涵信息:函数在哪调用(call-stack),函数怎么调用的,参数等等。record的一个属性就是this,指向函数执行期间的this对象。

  • this不是author-time binding,而是 runtime binding。
  • this的上下文基于函数调用的情况。和函数在哪定义无关,而和函数怎么调用有关。

2.1 this在具体情况下的分析

2.1.1 Global context

在全局上下文(任何函数以外),this指向全局对象。

console.log(this === window); // true
2.1.2 Function context

在函数内部时,this由函数怎么调用来确定。

2.1.2.1 Simple call

简单调用,即独立函数调用。由于this没有通过call来指定,且this必须指向对象,那么默认就指向全局对象。

function f1(){
  return this;
}

f1() === window; // global object

在严格模式下,this保持进入execution context时被设置的值。如果没有设置,那么默认是undefined。它可以被设置为任意值**(包括null/undefined/1等等基础值,不会被转换成对象)**。

function f2(){
  "use strict"; // see strict mode
  return this;
}

f2() === undefined;
2.1.2.2 Arrow functions

在箭头函数中,this由词法/静态作用域设置(set lexically)。它被设置为包含它的execution context的this,并且不再被调用方式影响(call/apply/bind)。

var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true

// Call as a method of a object
var obj = {foo: foo};
console.log(obj.foo() === globalObject); // true

// Attempt to set this using call
console.log(foo.call(obj) === globalObject); // true

// Attempt to set this using bind
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
2.1.2.3 As an object method

当函数作为对象方法调用时,this指向该对象。

var o = {
  prop: 37,
  f: function() {
    return this.prop;
  }
};

console.log(o.f()); // logs 37

this on the object's prototype chain

原型链上的方法根对象方法一样,作为对象方法调用时this指向该对象。

2.1.2.4 构造函数

在构造函数(函数用new调用)中,this指向要被constructed的新对象。

2.1.2.5 call和apply

Function.prototype上的callapply可以指定函数运行时的this

function add(c, d){
  return this.a + this.b + c + d;
}

var o = {a:1, b:3};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

注意,当用callapply而传进去作为this的不是对象时,将会调用内置的ToObject操作转换成对象。所以4将会装换成new Number(4)null/undefined由于无法转换成对象,全局对象将作为this

2.1.2.6 bind

ES5引进了Function.prototype.bindf.bind(someObject)会创建新的函数(函数体和作用域与原函数一致),但this被永久绑定到someObject,不论你怎么调用。

2.1.2.7 As a DOM event handler

this自动设置为触发事件的dom元素。

@creeperyang

This comment has been minimized.

Show comment
Hide comment
@creeperyang

creeperyang Feb 23, 2016

Owner

3. 作用域(Scope)和闭包(closure)

在第2部分对this的探讨中,我们已经部分涉及到了作用域,只是没有展开说,或者从作用域角度来说。

3.1 Scope是什么?

先尝试从几个方面描述下:

  • Scope这个术语被用来描述在某个代码块可见的所有实体(或有效的所有标识符),更精准一点,叫做上下文(context)或环境(environment)。
  • 当前执行的上下文(The current context of execution)。https://developer.mozilla.org/en-US/docs/Glossary/Scope

综合一下,Scope即上下文,包含当前所有可见的变量。

Scope分为Lexical Scope和Dynamic Scope。Lexical Scope正如字面意思,即词法阶段定义的Scope。换种说法,作用域是根据源代码中变量和块的位置,在词法分析器(lexer)处理源代码时设置。

让我们考虑下面的代码来分析Lexical Scope:

function foo(a) {
    //   inner scope 'foo'
    // defined argument a, and look-up b upwards
    console.log( a + b );
}

// outmost/global scope
// defined b
var b = 2;

foo( 2 ); // 4

Scope是分层的,内层Scope可以访问外层Scope的变量,反之则不行。上面的代码中即有嵌套Scope。用泡泡来比喻Scope可能好理解一点:

fig2

  1. 泡泡1是全局作用域,有标识符foo
  2. 泡泡2是作用域foo,有标识符a,bar,b
  3. 泡泡3是作用域bar,仅有标识符c

Scope在我们写代码的时候就被定义好了,比如谁嵌套在谁里面。

3.2 JavaScript Scope

JavaScript采用Lexical Scope

于是,我们仅仅通过查看代码(因为JavaScript采用Lexical Scope),就可以确定各个变量到底指代哪个值。

另外,变量的查找是从里往外的,直到最顶层(全局作用域),并且一旦找到,即停止向上查找。所以内层的变量可以shadow外层的同名变量。

3.2.1 Cheating Lexical

如果Scope仅仅由函数在哪定义的决定(在写代码时决定),那么还有方式更改Scope吗?JS有evalwith两种机制,但两者都会导致代码性能差。

3.2.1.1 eval

eval接受字符串为参数,把这些字符串当做真的在程序的这个点写下的代码——意味着可以编码方式来在某个点生成代码,就像真的在程序运行前在这里写了代码。

function foo(str, a) {
    eval( str ); // cheating!
    console.log( a, b );
}

var b = 2;
foo( "var b = 3;", 1 ); // 1, 3

默认情况下,eval会动态执行代码,并改变当前Scope。但非直接(indirectly)调用eval可以让代码执行在全局作用域,即修改全局Scope。

function bar(str) {
    (0, eval)( str ); // cheating in global!
}
bar('var hello = "hi";')

window.hello // "hi"

另外,严格模式下,eval运行在它自己的Scope下,即不会修改包含它的Scope。

function foo(str) {
   "use strict";
   eval( str );
   console.log( a ); // ReferenceError: a is not defined
}

foo( "var a = 2" );
3.2.1.1 with
function foo(obj) {
    with (obj) {
        a = 2;
    }
}

var o1 = {
    a: 3
};

var o2 = {
    b: 3
};

foo( o1 );
console.log( o1.a ); // 2

foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2 -- Oops, leaked global!

with以对象为参数,并把这个对象当做完全独立的Lexical Scope(treats that object as if it is a wholly separate lexical scope),然后这个对象的属性就被当做定义的变量了。

**注意:**尽管把对象当做Scope,var定义的变量仍然scoped到包含with的函数中。

不像eval可以改变当前Scope,with凭空创建了全新的Scope,并把对象传进去。所以o1传进去时可以正确更改o1.a,而o2传进去时,创建了全局变量a

3.3 Dynamic Scope?

上一节讲到,JS采用Lexical Scope,这里再明确一下:

JavaScript没有Dynamic Scope。

那么为什么又单开一节讲一下?

一是强调,二是,JS中的this机制跟Dynamic Scope很像,都是运行时绑定。

3.4 Function vs. Block Scope

上面的内容有意无意似乎应该表明了,JS没有Block Scope。

除了Global Scope,只有function可以创建新作用域(Function Scope)。 不过这已经是老黄历了,ES6引入了Block Scope。

{
    let x = 0;
}
console.log(x); // Uncaught ReferenceError: x is not defined

另外,withtry catch都可以创建Block Scope。

try {
    undefined(); // illegal operation to force an exception!
}
catch (err) {
    console.log( err ); // works!
}

console.log( err ); // ReferenceError: `err` not found
Owner

creeperyang commented Feb 23, 2016

3. 作用域(Scope)和闭包(closure)

在第2部分对this的探讨中,我们已经部分涉及到了作用域,只是没有展开说,或者从作用域角度来说。

3.1 Scope是什么?

先尝试从几个方面描述下:

  • Scope这个术语被用来描述在某个代码块可见的所有实体(或有效的所有标识符),更精准一点,叫做上下文(context)或环境(environment)。
  • 当前执行的上下文(The current context of execution)。https://developer.mozilla.org/en-US/docs/Glossary/Scope

综合一下,Scope即上下文,包含当前所有可见的变量。

Scope分为Lexical Scope和Dynamic Scope。Lexical Scope正如字面意思,即词法阶段定义的Scope。换种说法,作用域是根据源代码中变量和块的位置,在词法分析器(lexer)处理源代码时设置。

让我们考虑下面的代码来分析Lexical Scope:

function foo(a) {
    //   inner scope 'foo'
    // defined argument a, and look-up b upwards
    console.log( a + b );
}

// outmost/global scope
// defined b
var b = 2;

foo( 2 ); // 4

Scope是分层的,内层Scope可以访问外层Scope的变量,反之则不行。上面的代码中即有嵌套Scope。用泡泡来比喻Scope可能好理解一点:

fig2

  1. 泡泡1是全局作用域,有标识符foo
  2. 泡泡2是作用域foo,有标识符a,bar,b
  3. 泡泡3是作用域bar,仅有标识符c

Scope在我们写代码的时候就被定义好了,比如谁嵌套在谁里面。

3.2 JavaScript Scope

JavaScript采用Lexical Scope

于是,我们仅仅通过查看代码(因为JavaScript采用Lexical Scope),就可以确定各个变量到底指代哪个值。

另外,变量的查找是从里往外的,直到最顶层(全局作用域),并且一旦找到,即停止向上查找。所以内层的变量可以shadow外层的同名变量。

3.2.1 Cheating Lexical

如果Scope仅仅由函数在哪定义的决定(在写代码时决定),那么还有方式更改Scope吗?JS有evalwith两种机制,但两者都会导致代码性能差。

3.2.1.1 eval

eval接受字符串为参数,把这些字符串当做真的在程序的这个点写下的代码——意味着可以编码方式来在某个点生成代码,就像真的在程序运行前在这里写了代码。

function foo(str, a) {
    eval( str ); // cheating!
    console.log( a, b );
}

var b = 2;
foo( "var b = 3;", 1 ); // 1, 3

默认情况下,eval会动态执行代码,并改变当前Scope。但非直接(indirectly)调用eval可以让代码执行在全局作用域,即修改全局Scope。

function bar(str) {
    (0, eval)( str ); // cheating in global!
}
bar('var hello = "hi";')

window.hello // "hi"

另外,严格模式下,eval运行在它自己的Scope下,即不会修改包含它的Scope。

function foo(str) {
   "use strict";
   eval( str );
   console.log( a ); // ReferenceError: a is not defined
}

foo( "var a = 2" );
3.2.1.1 with
function foo(obj) {
    with (obj) {
        a = 2;
    }
}

var o1 = {
    a: 3
};

var o2 = {
    b: 3
};

foo( o1 );
console.log( o1.a ); // 2

foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2 -- Oops, leaked global!

with以对象为参数,并把这个对象当做完全独立的Lexical Scope(treats that object as if it is a wholly separate lexical scope),然后这个对象的属性就被当做定义的变量了。

**注意:**尽管把对象当做Scope,var定义的变量仍然scoped到包含with的函数中。

不像eval可以改变当前Scope,with凭空创建了全新的Scope,并把对象传进去。所以o1传进去时可以正确更改o1.a,而o2传进去时,创建了全局变量a

3.3 Dynamic Scope?

上一节讲到,JS采用Lexical Scope,这里再明确一下:

JavaScript没有Dynamic Scope。

那么为什么又单开一节讲一下?

一是强调,二是,JS中的this机制跟Dynamic Scope很像,都是运行时绑定。

3.4 Function vs. Block Scope

上面的内容有意无意似乎应该表明了,JS没有Block Scope。

除了Global Scope,只有function可以创建新作用域(Function Scope)。 不过这已经是老黄历了,ES6引入了Block Scope。

{
    let x = 0;
}
console.log(x); // Uncaught ReferenceError: x is not defined

另外,withtry catch都可以创建Block Scope。

try {
    undefined(); // illegal operation to force an exception!
}
catch (err) {
    console.log( err ); // works!
}

console.log( err ); // ReferenceError: `err` not found
@cdll

This comment has been minimized.

Show comment
Hide comment
@cdll

cdll Feb 25, 2016

长文必火!前排挤挤~

cdll commented Feb 25, 2016

长文必火!前排挤挤~

@yernsun

This comment has been minimized.

Show comment
Hide comment
@yernsun

yernsun Feb 25, 2016

男神起飞

yernsun commented Feb 25, 2016

男神起飞

@For-me

This comment has been minimized.

Show comment
Hide comment
@For-me

For-me Feb 25, 2016

_围观男神装逼!!!!_吓得我打字都歪了

For-me commented Feb 25, 2016

_围观男神装逼!!!!_吓得我打字都歪了

@Power-kxLee

This comment has been minimized.

Show comment
Hide comment
@Power-kxLee

Power-kxLee Feb 25, 2016

不明觉厉

Power-kxLee commented Feb 25, 2016

不明觉厉

@keifergu

This comment has been minimized.

Show comment
Hide comment
@keifergu

keifergu Apr 17, 2016

写的好,感觉理解又深了一些

keifergu commented Apr 17, 2016

写的好,感觉理解又深了一些

@moahmn

This comment has been minimized.

Show comment
Hide comment
@moahmn

moahmn May 7, 2016

文章很好啊谢谢

moahmn commented May 7, 2016

文章很好啊谢谢

@oychao

This comment has been minimized.

Show comment
Hide comment
@oychao

oychao Nov 10, 2016

为何要发到issue里面,直接写成文档多好~

oychao commented Nov 10, 2016

为何要发到issue里面,直接写成文档多好~

@creeperyang

This comment has been minimized.

Show comment
Hide comment
@creeperyang

creeperyang Nov 10, 2016

Owner

@CharlesOy 写issue里可以互动啊 😃

Owner

creeperyang commented Nov 10, 2016

@CharlesOy 写issue里可以互动啊 😃

@lilywang711

This comment has been minimized.

Show comment
Hide comment
@lilywang711

lilywang711 Dec 20, 2016

好棒~,很详细。复习了一遍,查缺补漏理解更深入了,谢谢 ~

lilywang711 commented Dec 20, 2016

好棒~,很详细。复习了一遍,查缺补漏理解更深入了,谢谢 ~

@liu3042

This comment has been minimized.

Show comment
Hide comment
@liu3042

liu3042 Mar 24, 2017

那如果函数的形参和函数声明的名字一样,该如何理解呢?

function test(arg){
	console.log(arg);  // [function : arg]
	var arg = 'hello';
	function arg(){
		console.log('hello world') 
	}
	console.log(arg);    // hello
}
test('hi');

liu3042 commented Mar 24, 2017

那如果函数的形参和函数声明的名字一样,该如何理解呢?

function test(arg){
	console.log(arg);  // [function : arg]
	var arg = 'hello';
	function arg(){
		console.log('hello world') 
	}
	console.log(arg);    // hello
}
test('hi');
@creeperyang

This comment has been minimized.

Show comment
Hide comment
@creeperyang

creeperyang Mar 24, 2017

Owner

@liu3042 可以看 变量的解析顺序(优先级),与变量进入作用域的4种方式的顺序一致。 相关部分。

function test(arg){
        // 1. 形参 arg 是 "hi"
        // 2. function arg声明提升,arg 是 function
	console.log(arg);  // [function : arg]
	var arg = 'hello';
        // 3. arg 此时是 "hello"
	function arg(){
		console.log('hello world') 
	}
	console.log(arg);    // hello
}
test('hi');
Owner

creeperyang commented Mar 24, 2017

@liu3042 可以看 变量的解析顺序(优先级),与变量进入作用域的4种方式的顺序一致。 相关部分。

function test(arg){
        // 1. 形参 arg 是 "hi"
        // 2. function arg声明提升,arg 是 function
	console.log(arg);  // [function : arg]
	var arg = 'hello';
        // 3. arg 此时是 "hello"
	function arg(){
		console.log('hello world') 
	}
	console.log(arg);    // hello
}
test('hi');
@oychao

This comment has been minimized.

Show comment
Hide comment
@oychao

oychao Mar 24, 2017

@liu3042 ,你的代码等价于:

function test(arg){
  var arg;
  arg = function(){
    console.log('hello world');
  }
  console.log(arg);
  arg = 'hello';
  console.log(arg);
}
test('hi');

oychao commented Mar 24, 2017

@liu3042 ,你的代码等价于:

function test(arg){
  var arg;
  arg = function(){
    console.log('hello world');
  }
  console.log(arg);
  arg = 'hello';
  console.log(arg);
}
test('hi');
@JackZhouMine

This comment has been minimized.

Show comment
Hide comment
@JackZhouMine

JackZhouMine Apr 9, 2017

好文,然而上下文和作用域并不是同一个概念。

JackZhouMine commented Apr 9, 2017

好文,然而上下文和作用域并不是同一个概念。

@creeperyang

This comment has been minimized.

Show comment
Hide comment
@creeperyang

creeperyang Apr 10, 2017

Owner

@JackZhouMine

2017-04-10 1 42 39

上面是MDN对Scope的定义,第一句就是 The current context of execution

The scope of a variable declared with var is its current execution context, which is either the enclosing function or, for variables declared outside any function, global. If you re-declare a JavaScript variable, it will not lose its value.

上面是 var 相关部分截取的,可以看到MDN这样定义:一个声明的变量的作用域,是它当前的执行上下文。

所以我觉得把 execution context 和 scope 等同起来理解没有问题(从实际使用来说,或者说至少从理解作用域这些来说没有问题)。

比如 http://davidshariff.com/blog/what-is-the-execution-context-in-javascript/ 这篇文章也把两者等同。

但上下文和作用域是不是同一个概念?我觉得这要看定义。有些规范上这两个概念应该是有区别的,所以你的话没有错。


补充:

http://stackoverflow.com/questions/7493936/is-there-a-difference-between-the-terms-execution-context-and-scope 是一个不错的解释。

Owner

creeperyang commented Apr 10, 2017

@JackZhouMine

2017-04-10 1 42 39

上面是MDN对Scope的定义,第一句就是 The current context of execution

The scope of a variable declared with var is its current execution context, which is either the enclosing function or, for variables declared outside any function, global. If you re-declare a JavaScript variable, it will not lose its value.

上面是 var 相关部分截取的,可以看到MDN这样定义:一个声明的变量的作用域,是它当前的执行上下文。

所以我觉得把 execution context 和 scope 等同起来理解没有问题(从实际使用来说,或者说至少从理解作用域这些来说没有问题)。

比如 http://davidshariff.com/blog/what-is-the-execution-context-in-javascript/ 这篇文章也把两者等同。

但上下文和作用域是不是同一个概念?我觉得这要看定义。有些规范上这两个概念应该是有区别的,所以你的话没有错。


补充:

http://stackoverflow.com/questions/7493936/is-there-a-difference-between-the-terms-execution-context-and-scope 是一个不错的解释。

@creeperyang

This comment has been minimized.

Show comment
Hide comment
@creeperyang

creeperyang Apr 10, 2017

Owner

稍微看了下ES6规范 Executable Code and Execution Contexts,可以看到,scope和规范中的 Lexical Environment 比较接近,而Execution Context要超出scope的概念,但scope是其重要的组成部分。

2017-04-10 4 59 21

Lexical Environment

Lexical Environment 是规范里的一种类型,用于定义 Identifiers 和 指定variables/functions 的关联(基于代码的词法嵌套结构lexical nesting structure of ECMAScript code)。

Lexical Environment由 Environment Record 和 一个可能为null的指向外层Lexical Environment的引用 组成。

通常,Lexical Environment和ECMAScript代码的特定词法结构由关,比如FunctionDeclaration, BlockStatement, Catch clause of a TryStatement,一旦这种代码执行,新的Lexical Environment被创建。

一个 Environment Record 记录了它所属 Lexical Environment 的 scope 内的 identifier bindings。

Environment Record 有两种基本的类别:declarative Environment Records 和 object Environment Records。

  1. Declarative Environment Records

一个 Declarative Environment Record 对应一个 ECMAScript program scope (scope包含 variable, constant, let, class, module, import, and/or function declarations)。Declarative Environment Record绑定了这个scope里所有声明定义的identifiers。

  1. Object Environment Records

  2. Function Environment Records

一个 function Environment Record 是用于表示一个函数的顶层scope的 Declarative Environment Record,并且,如果这个函数不是箭头函数,还提供 this 的绑定。

  1. Global Environment Records

Execution Context

An execution context is a specification device that is used to track the runtime evaluation of code by an ECMAScript implementation.

Execution Context的组成:

Component Purpose
code evaluation state Any state needed to perform, suspend, and resume evaluation of the code associated with this execution context.
Function ...
Realm ...
LexicalEnvironment Identifies the Lexical Environment used to resolve identifier references made by code within this execution context.
VariableEnvironment Identifies the Lexical Environment whose EnvironmentRecord holds bindings created by VariableStatements within this execution context.
Generator The GeneratorObject that this execution context is evaluating.
Owner

creeperyang commented Apr 10, 2017

稍微看了下ES6规范 Executable Code and Execution Contexts,可以看到,scope和规范中的 Lexical Environment 比较接近,而Execution Context要超出scope的概念,但scope是其重要的组成部分。

2017-04-10 4 59 21

Lexical Environment

Lexical Environment 是规范里的一种类型,用于定义 Identifiers 和 指定variables/functions 的关联(基于代码的词法嵌套结构lexical nesting structure of ECMAScript code)。

Lexical Environment由 Environment Record 和 一个可能为null的指向外层Lexical Environment的引用 组成。

通常,Lexical Environment和ECMAScript代码的特定词法结构由关,比如FunctionDeclaration, BlockStatement, Catch clause of a TryStatement,一旦这种代码执行,新的Lexical Environment被创建。

一个 Environment Record 记录了它所属 Lexical Environment 的 scope 内的 identifier bindings。

Environment Record 有两种基本的类别:declarative Environment Records 和 object Environment Records。

  1. Declarative Environment Records

一个 Declarative Environment Record 对应一个 ECMAScript program scope (scope包含 variable, constant, let, class, module, import, and/or function declarations)。Declarative Environment Record绑定了这个scope里所有声明定义的identifiers。

  1. Object Environment Records

  2. Function Environment Records

一个 function Environment Record 是用于表示一个函数的顶层scope的 Declarative Environment Record,并且,如果这个函数不是箭头函数,还提供 this 的绑定。

  1. Global Environment Records

Execution Context

An execution context is a specification device that is used to track the runtime evaluation of code by an ECMAScript implementation.

Execution Context的组成:

Component Purpose
code evaluation state Any state needed to perform, suspend, and resume evaluation of the code associated with this execution context.
Function ...
Realm ...
LexicalEnvironment Identifies the Lexical Environment used to resolve identifier references made by code within this execution context.
VariableEnvironment Identifies the Lexical Environment whose EnvironmentRecord holds bindings created by VariableStatements within this execution context.
Generator The GeneratorObject that this execution context is evaluating.
@creeperyang

This comment has been minimized.

Show comment
Hide comment
@creeperyang

creeperyang Aug 9, 2017

Owner

发现第 3 点中闭包被漏掉了,这里补上。

闭包(closure)

A closure is the combination of a function and the lexical environment within which that function was declared.

在 JavaScript 中,函数形成闭包。闭包就是函数和函数声明时的词法作用域的组合。

下面用例子说明:

function outer () {
  var name = 'Jerry'
  function inner () {
    console.log(name)
  }
  return inner
}

var fn = outer()
fn() // Jerry

在部分编程语言中,函数内部的局部变量仅仅存在于函数执行期间,一旦函数执行完毕,变量就销毁(不再能访问)。

但在 JavaScript 中,由于闭包的原因,fn 仍可以访问 name 变量。

Owner

creeperyang commented Aug 9, 2017

发现第 3 点中闭包被漏掉了,这里补上。

闭包(closure)

A closure is the combination of a function and the lexical environment within which that function was declared.

在 JavaScript 中,函数形成闭包。闭包就是函数和函数声明时的词法作用域的组合。

下面用例子说明:

function outer () {
  var name = 'Jerry'
  function inner () {
    console.log(name)
  }
  return inner
}

var fn = outer()
fn() // Jerry

在部分编程语言中,函数内部的局部变量仅仅存在于函数执行期间,一旦函数执行完毕,变量就销毁(不再能访问)。

但在 JavaScript 中,由于闭包的原因,fn 仍可以访问 name 变量。

@leirt97

This comment has been minimized.

Show comment
Hide comment
@leirt97

leirt97 Sep 12, 2017

闭包就是函数和函数声明的词法作用域的组合,这句话有点难懂...

leirt97 commented Sep 12, 2017

闭包就是函数和函数声明的词法作用域的组合,这句话有点难懂...

@creeperyang

This comment has been minimized.

Show comment
Hide comment
@creeperyang

creeperyang Sep 12, 2017

Owner

函数和函数声明时的词法作用域形成闭包 ——这样可能好理解一点。

Owner

creeperyang commented Sep 12, 2017

函数和函数声明时的词法作用域形成闭包 ——这样可能好理解一点。

@Hibop

This comment has been minimized.

Show comment
Hide comment
@Hibop

Hibop Jan 24, 2018

大神您好,构造函数的call调用比较复杂 能请教下吗?

function add(c, d){
  this.d=d;  // set调用对象o
  return this.a + this.b + c + d;  // get调用对象o
}

var o = {a:1, b:3};
add.call(o, 5, 7);
console.log(o);

Hibop commented Jan 24, 2018

大神您好,构造函数的call调用比较复杂 能请教下吗?

function add(c, d){
  this.d=d;  // set调用对象o
  return this.a + this.b + c + d;  // get调用对象o
}

var o = {a:1, b:3};
add.call(o, 5, 7);
console.log(o);
@creeperyang

This comment has been minimized.

Show comment
Hide comment
@creeperyang

creeperyang Jan 24, 2018

Owner

@Hibop 不是很明白你的问题。没有所谓的构造函数和非构造函数,在用new 调用函数时,函数就是构造函数。

这里 add.call(o, 5, 7); 时,this 指向 o,就是这样的。

Owner

creeperyang commented Jan 24, 2018

@Hibop 不是很明白你的问题。没有所谓的构造函数和非构造函数,在用new 调用函数时,函数就是构造函数。

这里 add.call(o, 5, 7); 时,this 指向 o,就是这样的。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment