与其他语言相比,函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。在绝大多数情况下,函数的调用方式决定了this的值。this不能在执行期间被赋值,并且在每次函数被调用时this的值也可能会不同。
全局上下文
无论是否在严格模式下,在全局执行上下文中(在任何函数体外部)this 都指代全局对象。
console.log(this);// Window
var name = "Thresh";
console.log(window.name);// Thresh
函数上下文
在函数内部,this的值取决于函数被调用的方式。
因为下面的代码不在严格模式下,且 this 的值不是由该调用设置的,所以 this 的值默认指向全局对象。
function f1(){
return this;
}//在浏览器中:f1() === window;
然而,在严格模式下,this将保持他进入执行上下文时的值,所以下面的this将会默认为undefined。
function f2(){
"use strict"; // 这里是严格模式
return this;
}
console.log(f2());// undefined
所以,在严格模式下,如果 this 没有被执行上下文(execution context)定义,那它将保持为 undefined。
当函数作为对象里的方法被调用时,它们的 this 是调用该函数的对象。
下面的例子中,当 o.f()被调用时,函数内的this将绑定到o对象。
var man = {
name:"李狗蛋",
fn:function(){
console.log(this.name); //李狗蛋
}
}
man.fn();
下面来道笔试题
var x = 10;
function fn2() {
alert(this.x)
}
var obj = {
x: 20,
fn: function() {
alert(this.x)
}
}
var fn1 = obj.fn;
fn1(); // 10
fn2(); // 10
没错,最终输出的都是全局的 10。
这里再次强调一点,this的指向在函数创建的时候是决定不了的,在调用的时候才能决定,谁调用的就指向谁,一定要搞清楚这个。
这道题是不是挺简单的,让我们提升点难度吧
function foo(){
console.log(this.a)
}
var a = 3;
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 输出2,因为是obj调用的foo,所以foo的this指向了obj,而obj.a = 2
如果存在多次调用,对象属性引用链只有上一层或者说最后一层在调用位置中起作用,如:
function foo() {
console.log( this.a )
}
var obj2 = {
a: 42,
foo: foo
}
var obj1 = {
a: 2,
obj2: obj2
}
obj1.obj2.foo(); // 42
这里有一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,我们把上面的题目稍微修改下
function foo() {
console.log(this.a)
}
var obj2 = {
a: 42,
foo: foo
}
var obj1 = {
a: 2,
obj2: obj2
}
var a = 32;
var bar = obj1.obj2.foo;
bar();//32
这里的答案是32因为bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定
为了让this确认指向,可以利用call、apply、bind、new绑定等
apply方法
/*定义一个人类*/
function Person(name, age) {
this.name = name;
this.age = age;
}
/*定义一个学生类*/
function Student(name, age, grade) {
Person.apply(this, arguments);
this.grade = grade;
}
//创建一个学生类
var student = new Student("lisi", 18, "一年级");
alert("name:" + student.name + "\n" + "age:"
+ student.age + "\n" + "grade:" + student.grade);
//name:lisi age:18 grade:一年级
分析: Person.apply(this,arguments);
this:在创建对象在这个时候代表的是student
arguments:是一个数组,也就是[“lisi”,”18”,”一年级”];
也就是:用student去执行Person这个类里面的内容,在Person这个类里面存在this.name等之类的语句,这样就将属性创建到了student对象里面
在Studen函数里面可以将apply中修改成如下:
Person.call(this,name,age);
call与apply方法的异同
call()
function.call(obj[,arg1[, arg2[, [,.argN]]]]])
· 调用call的对象必须是个函数function
· call的第一个参数将会是function改变上下文后指向的对象,也就是上面例子里的小刚,也就是上上面例子里的老婆大人,如果不传,将会默认是全局对象window
· 第二个参数开始可以接收任意个参数,这些参数将会作为function的参数传入function
· 调用call的方法会立即执行
apply()
function.apply(obj[,argArray])
与call方法的使用基本一致,但是只接收两个参数,其中第二个参数必须是一个数组或者类数组,这也是这两个方法很重要的一个区别
bind方法
ECMAScript 5 引入了 Function.prototype.bind。调用f.bind(someObject)会创建一个与f具有相同函数体和作用域的函数,但是在这个新函数中,this将永久地被绑定到了bind的第一个参数,无论这个函数是如何被调用的。
function f() {
return this.a;
}
var g = f.bind({a: "花花"});
console.log(g()); // 花花
var h = g.bind({a: 'yoo'}); // bind只生效一次!
console.log(h()); // 花花