引言
执行上下文 = 执行 + 上下文 ;
也就是说执行上下文, 必然是伴随这执行的发生.
那么怎么才能执行?
- 浏览器解析到
<script>
标签时或者成功加载外部脚本后, 会逐行执行, 也叫全局执行. - 在逐行读到函数调用时, 也会执行, 这是函数的执行.
- 还有逐行读到比较特殊的 eval 语句, 也会执行 , 暂且称作 eval 执行.
相应的执行则会生成不同的上下文, 什么叫做上下文? 在做阅读理解的时候 , 理解一句话的含义的时候, 就通常要结合上下文 , 那么程序要正确执行的时候 , 也要结合程序执行时的上下文 , 也叫做程序执行环境. 不同环境不同的执行效果.
- 全局执行 => 全局执行上下文
- 函数执行 => 局部执行上下文
- eval 执行 => eval 执行上下文
运行流程
上面说 , 逐行读取执行, 并不完全正确.
因为 js 在执行之前, 是会预编译的. 也就是:
- 全局环境
<script>
代码加载完毕 => 预编译 => 收集所有声明(变量和函数) => 无语法错误 => 生成全局执行上下文 => 开始执行 => 逐行执行( 跳过声明 ) => 执行完毕
其中执行完毕 , 但是全局执行上下文却并未销毁, 只有在关闭网页或者浏览器的时候, 全局执行上下文才会销毁.
- 函数局部环境
而上面流程中的 , 逐行执行( 跳过声明 )中 , 如果是纯赋值, 直接赋值, 然后跳到下一行. 但是如果碰到函数执行, 则会进入函数体 , 等函数执行完毕并返回 , 才能继续下一行 .
那么进入函数体 , 流程基本和全局类似:
调用函数 => 函数体内预编译 => 收集所有声明(变量和函数) => 无语法错误 => 生成函数局部执行上下文 => 开始执行 => 逐行执行 => 执行完毕 => 返回结果 , 跳出函数
执行上下文是什么?
- 产生条件?
代码执行即产生执行上下文. 每次执行都会重新生成.
1. 浏览器刷新 => 全局上下文重新生成
2. 函数每次调用 => 函数执行上下文重新生成
- 本质是什么? 包括哪些?
执行上下文本质是一个对象 .
该对象大致包括了三个内容:
1. 参数表
2. this
3. 局部变量和局部函数声明
- 执行上下文中的 VO/AO ?
VO 和 AO 为不同时期的同一对象
1. VO 也叫变量对象, 是预编译时, 参数表 + 局部变量和局部函数声明
2. AO 也叫活动对象, 是执行时 , 和 VO 为同一个对象.
- 生成执行上下文时确定作用域链?
作用域链就是定义了查找变量查找的规则 , 变量查找由两部分组成 , 一部分为局部变量, 也就是 VO/AO ; 还有一部分 , 则是函数上的 [[Scopes]]
属性 , 在预编译阶段生成.
- 浏览器看执行上下文
函数执行上下文
VO/AO
[[Scopes]]
this 指向
- 简单函数内为 window
function test(){
console.log(this);
inner();
function inner(){
console.log(this);
}
}
test(); // window , window
- 严格模式下的普通函数 this 为 undefined
function test(){
'use strict';
console.log(this);
inner();
function inner(){
console.log(this);
}
}
test(); // undefined , undefined
// 如果显示指定方法拥有者
window.test(); // window , undefined
- 显式绑定, bind , call , apply , 绑定后 this 指向绑定的对象
var name = 'jack'
var obj = {
name : 'xx'
}
function test(){
console.log(this.name);
}
// bind
var bindTest = test.bind(obj);
bindTest(); // 'xx'
// apply
test.apply(obj); // 'xx'
// call
test.call(obj); // 'xx'
// 当 apply 传入 null , undefined 时, 非严格模式下,this 指向全局对象,严格模式指向传入的值( null / undefined )
test.apply(null); // 'jack'
- new 操作时的 this 指向实例
function CreateObj(name,age){
this.name = name ;
this.age = age;
}
var obj = new CreateObj('xx',18);
console.log(obj.name); // 'xx'
- 对象中的方法内的 this 指向方法拥有者
var name = 'jack'
var obj = {
name:'john',
test:function(){
'use strict';
console.log(this.name);
}
}
obj.test(); // 'john'
var testFn = obj.test;
window.testFn(); // 'jack'
testFn(); // this 为undefined , this.name 查找报错
- 函数里面的函数调用 , 非严格模式下 , this 指向 window
// 1. 具名函数
function test(){
function inner(){
console.log(this);
}
inner();
}
test(); // window
// 2. 匿名函数
function test2(){
(function(){
console.log(this);
})()
}
test2(); // window
// 3. 对象里面的函数的函数
var obj = {
test:function(){
console.log(this);
function inner(){
console.log(this);
}
inner();
}
}
obj.test(); // obj , window
- 箭头函数的 this, 箭头函数的 this 沿用外一层的 this 指向
var obj = {
test:function(){
console.log(this);
var inner = ()=>{
console.log(this);
}
}
}
obj.test(); // obj , obj
- setTimeout / setInterval 的回调的 this , 非严格模式指向 window
var obj = {
test:function(){
console.log(this);
setTimeout(function(){
console.log(this);
},1000)
}
}
obj.test(); // obj , window
- dom 事件绑定的 this, 指向触发元素
<button id="click-test">click</button>
<script>
document.querySelector('#click-test').onclick = function () {
console.log(this); // button 元素
}
</script>
总结: this 之所以诸多变化, 那是因为在函数执行时, 执行上下文都是重新生成的. 而执行上下文的变化 , 则是根据调用的不同, 而赋值不同.
而最简单粗暴的, 就是 this 的指向始终指向 函数方法
的拥有者. 也就是方法前面的点的对象是谁, 比如 obj.test()
, 那么 test
函数的执行上下文的 this
指向 obj
;
在非严格模式下 , 全局函数的 this
指向 window
, 严格模式下为 undefined
, 但是严格模式下通过 window.方法
, 又能将 this
指向 window` ;
在非严格模式下 , 不能明确找到函数方法拥有者 , 比如函数内部匿名函数或局部函数的 this
指向 window , 严格模式指向 undefined
.
而 DOM
绑定的方法, 则认为拥有者为 DOM
元素.
call
, apply
临时改变了拥有者;
bind
永久改变了拥有者.
所以 , 方法拥有者就是 this
指向的值. ( 接受反驳 );