一次有意思的JavaScript试验题

今天群里的朋友发了一段代码,问最后的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var a=1;
var b={
a:2,
b:function(){
alert(this.a);
}(),
f:this.f=function(){
alert(this.a);
}
};
function f(){
alert(3);
}
f();
b.f();
(b.f)();
(0, b.f)();

在浏览器中运行的结果是1,1,2,2,1
跟我自己脑补的不一样…可能是技术还不够吧…于是在浏览器中运行并且分析了下.

第一次a是因为匿名函数自执行的缘故,this指向window,那么this.a就是window.a于是会弹出第一个1

f()

在定义的时候也就是创建作用域的时候是在window下创建的,那么因为函数声明的缘故,首先是
function f(){}被声明 随后在定义b的时候发现有个this.f,因为未在b中调用也就是说在创建作用域的时候
还未有b这个作用域,那么this.f就等于window相当于window.f=function(){}
简单来说就是在创建b作用域的时候this是指向window的因为b作用域还未创建.于是覆盖了function f(){}弹出的是1

对象并没有作用域,其中this.f可以这样写

1
2
3
4
5
6
7
8
9
var a=888;
var o={
a:24
}
this.f=function(){
console.log(this.a)
}
o.f=this.f
o.f()

对象设置属性有两种一种是var o={a:24};还有一种是 var o={}; o.a=24;
即上述的代码改写成下例this就指向正确了,参考蒋大神的,this跟作用域并没有关系,this指向的是当前调用函数的所有者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var a=888;
function ObjectF(){
var o={
a:24,
f:this.f=function(){
console.log(this.a)
}
}
return o;
}
var fc=new ObjectF();
fc.f()//24

(b.f)()

在匿名函数中,this始终指向window,
b.f其中this.f=function(){}这个this指向的是b.因为是b.f调用的,没指向window

(0,b.f)()

这个有点意思,匿名函数检测到多个函数会调用apply(window,arguments),于是每个函数的作用域都变了就算你指向的是b 在运行的时候会调用apply方法.
要实现这个效果其实挺简单的(b.f).bind(window)() 这样就改变了指向 类似于b.f.bind(window)

另外(0,b.f)()之所以指向window,应该是因为“,”逗号操作符的影响,逗号操作符的规则是求值,并且等于逗号右侧的值,函数被求值后就是函数本身了,没有了调用者,指向window

个人分析和理解~大牛轻点


以下是蒋大神的回复

b变量是对象,只是最内层作用域的一部分,它没有作用域。赋值操作时先右后左。this独立于作用域链指向函数调用者,这里只有赋值没有调用,即使有,也是遵循先右后左跟对象无关。
作用域链是function被解析时的[[scope]]属性,函数被调用时会引用该[[scope]],产生一个活动对象,并加上自己内部的环境一并构成作用域链。全局就是一个活动对象,这里分析f()时出现的 this.f,外层的活动对象是全局活动对象。
我们研究的作用域链上都是基于活动对象的引用,在活动对象里才会有解析的过程,才会产生函数对象,从而产生[[scope]],scope上引用的都是活动对象。
每个函数只有被解析时有一个函数对象,却可以有很多活动对象,之所以可以存在很多,是因为有函数对象引用了该活动对象的作用域并挂载在了外层变量上
this是独立于这个系统的,在函数执行生成活动对象时被初始化,指向调用者或window,作为该活动对象最内侧作用层的一个变量。

另外(0,b.f)()之所以指向window,应该是因为“,”逗号操作符的影响,逗号操作符的规则是求值,并且等于逗号右侧的值,函数被求值后就是函数本身了,没有了调用者,指向window

活动对象初始化的时候内部作用域会扫描函数声明、var定义的函数、形参、arguments、this,形成自己的环境,然后加上该活动对象对应的函数对象的[[scope]],所以this的指定跟[[scope]]没有关系,scope上的每一层都是活动对象,活动对象都有环境,都有自己的this。执行流继续往下执行到对象,只会改变变量的赋值,扫描到函数,又会生成新的函数对象,产生[[scope]]属性,这个属性就是该活动对象的作用域。该函数被调用时会产生活动对象重复该过程

我想说明:
①this跟作用域链没关系,他是当前作用域环境的一份子
②对象跟作用域也没关系,他赋给的变量也是当前作用域环境的一份子
③函数不管定义在哪里,对象里还是别处,执行到都会初始化产生作用域链引用,一旦挂载外部,函数退出也不会销毁啦
④函数初始化为函数对象,函数体会作为一个属性,内部细节不会被扫描解析产生化学反应的。只在执行产生活动对象才会进入内部

所以有些现象不是对象引起的,只是对象里刚好赋值了函数,都是函数的化学反应

恶补了一下JavaScript 接下来分析下

全局初始化阶段

1
2
3
4
5
AO(global){
a:undefined,
b:undefined,
f:<ref to func 'f'>,
}

全局执行阶段

为了方便了解 这里换一种写法

1
2
3
4
5
6
7
8
9
AO(global)[a]=1;
AO(global)[b]={};
AO(global)[b].a=2;
AO(global)[b].b=func() //函数没有声明return 都是返回undefined;
AO(global)[b].f=this.f;//在全局中执行 this指向global,覆盖f
AO(global).f()//因为覆盖了f所以弹出的是1 this指向window
AO(global)[b].f() //this指向的是b
AO(global)(b.f)() //检测到匿名函数则先声明

匿名函数初始化阶段

1
2
3
AO(b.f){
b.f:<ref to func 'b.f'>
}

匿名函数执行阶段

1
AO(b.f).alert(this.a)// this指向b所以会弹出b.a

全局执行阶段-1

1
AO(global).(0,b.f)()

匿名函数初始化阶段 (同上略)

匿名函数执行化阶段

首先.号优先级比较高,那么右侧的是
this.f=function(){alert(this.a)};
再运算,号操作符然后会返回这个函数本身也就是
(this.f=function(){alert(this.a)})()
这段函数运行结果就是去找global下的a
,号操作符参考文章