01 context
任何方法,谁调用了它,则就是它的context。
这个和this的描述是一致的:
this最终指向的是调用它的对象
上下文就是this的指向。
02 不稳定的context
context在js的表现经常出现一些看似"不稳定"的情况,下面列出了一些"不稳定"。
(1) new和非new
下面是一道常见的面试题,问①和②分别输出什么结果?
function say() {
console.log(this);
};
new say(); // ①
say(); // ②
虽然简单,却是对context最全的理解。
①输出 say 的实例
②输出window吗?
显然我们凭借这段代码是无法做出对②的判断,因为我们根本不知道say()的运行环境是什么。
(2) 方法赋值
下面试题,将方法赋值给一个变量。
var name = "嗷嗷嗷";
var cat = {
name: "喵",
say: function() {
console.log(this.name);
}
}
var say = cat.say;
cat.say(); // ①
say(); // ②
结果:
①输出 喵
②输出 嗷嗷嗷
这里可以理解:因为say其实只是引用了一个函数,函数的运行依赖context,不同的context必然有不同结果。
(3) Event Loop
Event Loop又叫事件循环,由于js没有多线程,处理多个任务的时候,就需要采用异步队列(如定时器、I/O、Promse),或者利用浏览器的多线程启动多个任务(如ajax)。
那么异步任务,它的context就是浏览器当前打开的窗口了,即window。
下面是一个常见的面试问题:
function say() {
setTimeout(function() {
console.log(this);
}, 0);
};
new say(); //①
say(); //②
①和②都输出window。
(4) 自执行函数
看下面一个问题:
function say() {
(function() {
console.log(this);
})();
};
new say(); //①
say(); //②
①和②都输出window,这里也说明了自执行函数是被window调用。
03 闭包拯救世界
面对不稳定的context,闭包可以被动解决这些问题。
因为我们所期望的context,和闭包所表现的scope惊人相似。
于是我们纷纷这样做:
function say() {
var _this = this;
setTimeout(function() {
console.log(_this);
}, 0);
};
new say(); //①
say(); //②
function say() {
(function(_this) {
console.log(_this);
})(this);
};
new say(); //①
say(); //②
上面做法用_this代替this,使得正常访问外层this。
04 bind来优化
对于用闭包来改变context的问题,不是很优雅。
用bind会更简洁。
实现是下面代码:
function say() {
setTimeout(function() {
console.log(this);
}.bind(this), 0);
};
new say(); //①
say(); //②
通过bind直接改变当前函数的context,这样子做法是符合我们的阅读习惯的。
05 call和apply
call和apply又叫对象冒充,是在方法执行的时候,传入一个对象,顶替原有的context。
下面代码通过call,主动改变的执行函数的context。这在封装里面很重要。
<div onclick="clickdiv.call(this)">click me</div>
<script type="text/javascript">
function clickdiv() {
console.log(this);
}
</script>
点击div,打印div元素。
06 箭头函数
可能意识到context这个问题,于是es6推出箭头函数。箭头函数采用词法作用域,这使得它和闭包的解决极为相似。
注意:arguments本身在箭头函数下,会指向外层函数的arguments (同this一样)。
function say() {
setTimeout(() => {
console.log(this);
}, 0);
};
new say(); //①
say(); //②
这里①和②都正常输出了。
07 Arguments
Arguments也是context绕不过去的坎,Arguments是函数的内部对象,js在执行函数的时候,会根据Arguments初始化函数内部的变量。
arguments.callee可以访问到当前函数。
arguments.callee.caller可以访问调用当前函数的函数。
arguments上面特性严格模式下是不允许被使用的。