先推荐几篇微信文章链接,有兴趣的小伙伴可以看看。
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();
来源:CSDN
作者:呢个啊飞
链接:https://blog.csdn.net/qqchenyufei/article/details/82795713