说说JS的执行上下文

旧城冷巷雨未停 提交于 2019-12-04 17:41:09

先推荐几篇微信文章链接,有兴趣的小伙伴可以看看。

1、内存空间详细图解 http://mp.weixin.qq.com/s/NGqdjhoU3MR9LD0yH6tKIw
2、执行上下文详细图解 http://mp.weixin.qq.com/s/hRE3HzeSxxok1bLI8vH1yw
3、变量对象详解 http://mp.weixin.qq.com/s/LijjPErxcFB4pN_wUo2cnw
4、作用域链与闭包 https://mp.weixin.qq.com/s/taddUMUOcPgAriW6xZWFcA
5、全方位解读this http://mp.weixin.qq.com/s/rlFJAiD1YWb065juEe4sNg
6、this的值到底是什么?一次说清楚 http://mp.weixin.qq.com/s/ksqtCd8ouxU-cVc_HnA4Aw


1、内存空间

内存空间大家都比较熟,即栈与堆。
JS的基础数据类型有Undefined、Null、Boolean、Number、String,这些都是按值访问,存放在栈内存。
其他的Object为引用类型,如数组Array或其他的自定义对象,这些存放在堆内存,其对应的地址引用(指针)放在栈内存。
大家对这些应该比较熟,就不赘述了。

2、JS代码的执行环境(执行上下文,Execution Context,下面简称EC)

JS是单线程的,运行在全局EC,每进入一个function,就做一次入栈操作,向栈顶压入一个属于该function的新的EC。若function中又调用了另一个function,则再执行一次入栈…依次执行完再依次出栈,回到全局EC。全局EC一定是在栈底,在浏览器关闭后出栈。

执行上下文,出入栈图解:
在这里插入图片描述

EC的构成如下图:
在这里插入图片描述
变量对象VO(Variable Object)保存此EC中涉及到的变量。
作用域链保存着此EC中的VO与其他EC中的VO的关联关系(能否访问到)。
然后是this,在EC被创建时,会确定this的指向。
EC的创建过程可以用以下代码表示:

		testEC = {
            VO: {},
            scopeChain: {},
            this: {}
        }

3、变量对象VO

function test1(arg){
    var a = 1;
    var b = {name: 'afei'};
    function c(){
        console.log(a);
    }
}

上面test1执行时创建的变量对象如下:

VO = {
    arguments: {...},
    a: 1,
    b: <b reference>,
    c: <c reference>
}

4、作用域链

执行环境EC中的scopeChain(作用域链),是由当前内存中各个变量对象VO串起来的单向链表,每入栈执行一个function,其对应的VO就添加到作用域链的头部,前一个VO能自由访问到下一个VO上的变量,反过来就不行。
即下面例子的VO(innerTest)能顺着单向链访问到VO(test),所以能访问到test中定义的b。反过来VO(test)在链中的位置无法访问到VO(innerTest),所以test无法访问到innerTest中定义的c。

还有,innerTest要访问全局的a,需要经由VO(innerTest)>VO(test)>VO(global)的路径才访问得到。这就是为什么我们说innerTest中找不到的变量,会先到test中先找,再到global中找,就是顺着这条链来找的。
尽量少在function中引用全局变量,也是为了避免function嵌套太多导致链条太长而引发性能问题。

var a = 20;
 
function test() {
    var b = a + 10;
 
    function innerTest() {
        var c = 10;
        return b + c;
    }
 
    return innerTest();
}
 
test();


innerTestEC = {
    VO: {...},  // 变量对象
    scopeChain: [VO(innerTest), VO(test), VO(global)], // 作用域链
    this: {}
}

5、作用域链与闭包

var fn = null;
function foo() {
    var a = 2;
    function innnerFoo() {
        console.log(a);
    }
    fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn
}
 
function bar() {
    fn(); // 此处的保留的innerFoo的引用
}
 
foo();
bar(); // 2

在上面的例子中,foo()执行完毕之后,按照常理,其执行环境生命周期会结束,所占内存被垃圾收集器释放。但是通过fn = innerFoo,函数innerFoo的引用被保留了下来,复制给了全局变量fn。这个行为,导致了foo的变量对象,也被保留了下来。于是,函数fn在函数bar内部执行时,依然可以访问这个被保留下来的变量对象。所以此刻仍然能够访问到变量a的值。

这样,我们就可以称foo为闭包。

下图展示了闭包fn的作用域链:
在这里插入图片描述

6、闭包思考:

这里拿setTimeout做例子。利用闭包修改下面的代码,让循环输出的结果依次为1, 2, 3, 4, 5。

for (var i=1; i<=5; i++) {
    setTimeout( function timer() {
        console.log(i);
    }, i*1000 );
}

7、this:

(1)this的指向,是在函数被调用的时候确定的。也就是执行上下文被创建时确定的。因此我们可以很容易就能理解到,一个函数中的this指向,可以是非常灵活的。比如下面的例子中,同一个函数由于调用方式的不同,this指向了不一样的对象,在函数执行过程中,this一旦被确定,就不可更改了。
(2)在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。如果函数独立调用,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。
(3)可以使用call,apply显式指定this。

简单总结一下:哪个对象调用的方法(object.method()),this就指向谁;独立调用时,指向window(严格模式下指向undefined);通过call,apply,bind可以显示指定this。

setTimeout,setInterval的回调函数中,this指向window。(eval也比较特殊,有兴趣的自己研究一下)

下面是一些关于this的例子:

//demo01
var a = 20;

function fn(){
	console.log(this.a);
}

fn();


//demo02
var a = 20;
function fn(){
	function foo(){
		console.log(this.a);
	}
	foo();
}
fn();


//demo03
var a = 20;
var obj = {
	a: 10,
	c: this.a + 20,
	fn: function(){
		return this.a;
	}
}
console.log(obj.c);
console.log(obj.fn());

//demo call
function fn() {
    console.log(this.a);
}
var obj = {
    a: 20
}
 
fn.call(obj);


//demo end
function foo(){
	console.log(this.a);
}
function active(fn){
	fn();
}
var a = 20;
var obj = {
	a: 10,
	getA: foo
}
active(obj.getA);



//原型中的this
function Person(name, age){
	this.name = name;
	this.age = age;
}

Person.prototype.getName = function(){
	return this.name;
}

var p1 = new Person('Nick', 20);
p1.getName();
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!