1.引子:作用域与上下文
总的来说:作用域与调用函数访问变量的能力有关,上下文和this关键字有关,是当前可执行代码的引用。
1.1作用域
作用域分为局部作用域和全局作用域,作用域往往和变量存在关系。处在局部作用域里面可以访问全局作用域的变量,而处在局部作用域外面不能访问局部作用域里面的变量。看代码:
var globalVariable = 'this is global variable';
function globalFunction(){
var localVariable = 'this is local variable';
console.log(globalVariable); //this is global variable
console.log(localVariable); //this is local variable
globalVariable = 'globalVariable has changed';
function innerFunction(){
var innerLocalVariable = 'this is inner local variable';
console.log(globalVariable); //globalVariable has changed
console.log(localVariable); //this is local variable
console.log(innerLocalVariable); //this is inner local variable
}
innerFunction();
}
globalFunction();
1.2上下文
上下文常常代表this变量的值以及它的指向,它决定一个函数怎么被调用,当一个函数被作为一个对象的方法调用时,this总是指向调用方法的对象。
var pet = {
words: '...',
speak: function(){
console.log(this.words); //'...'
console.log(this === pet); // true
}
}
pet.speak();
'==='表示不仅值相等,类型也相同,this指向调用speak方法的pet对象,对比下面代码:
function pet(words){
this.words = words;
console.log(words); //'...'
console.log(this === pet) //false
console.log(this === global) //true
}
pet('...');
此处的this指向运行环境的顶层的global对象,这里可以直接使用console.log(this)查看。继续看一段代码:
function Pet(words){
this.words = words;
this.speak = function(){
console.log(this.words); //miao
console.log(this); //打印出当前对象{words:'miao',speak:[Function]}
}
}
var cat = new Pet('miao');
cat.speak();
*注:
引入箭头函数=>
后,this不是指向函数调用的对象, 而是其父级对象
var words= 'global';
var pet = {
words: '...',
speak: () => {
console.log(this.words); //'global'
console.log(this === pet); // false
}
}
pet.speak(); // pet的父级对象Window
2.call和apply
使用call和apply可以改变上下文执行对象,可以在自定义上下文中执行函数,两者作用相同,仅仅是方法的第二个参数不同,call直接使用参数列表,apply使用参数数组。具体作用是调用一个对象的方法,以另一个对象替换当前对象,实际上市改变this指向的对象内容。看代码:
var pet = {
words: '...',
speak: function(say){
console.log(say + ' ' + this.words);
}
}
pet.speak('Speck'); // Speck ...
没毛病!,修改代码,再看一下:
var pet = {
words: '...',
speak: function(say){
console.log(say + ' ' + this.words);
}
}
var dog = {
words:'wang'
}
pet.speak.call(dog,'Speak'); // Speak wang
dog对象本来没有speak方法,通过call的方法,偷梁换柱地将原本指向pet的this变量指向了dog,因此在运行时,this.words='wang',所以打印出Speak wang。通过这种方式,我们可以使一个对象去使用另一个对象的方法,一个容易想到的应用场景就是继承。
function Pet(words){
this.words = words;
this.speak = function(){
console.log(this.words);
}
}
function Dog(words){
//Pet.call(this,words); // Pet中的this指向当前的Dog,使Dog拥有Pet的方法
Pet.apply(this,[words]); // apply中的第二个参数是一个数组
}
var dog = new Dog('wang');
dog.speak();
我们可以看到,使用call和apply实现的效果是一样的,仅仅是参数的差异,一个直接使用words,一个要使用数组形式的[words]。再看一个例子:
function print(a, b, c, d){
alert(a + b + c + d);
}
function example(a, b , c , d){
//用call方式借用print,参数显式打散传递
print.call(this, a, b, c, d);
//用apply方式借用print, 参数作为一个数组传递,
//可以直接用JavaScript方法内本身有的arguments数组
print.apply(this, arguments);
//或者封装成数组
print.apply(this, [a, b, c, d]);
}
至于什么时候使用call,什么时候使用apply,一般当参数比较明确的时候,两者都可以,当参数不明确时,推荐使用apply+arguments的方式。