this对象
this关键字是javascript中最复杂的机制之一,它是一个很特别的关键字,
所有函数都具有的对象之一,另外一个就是arguments;
为什么我们特别关心this呢?
因为this给我们提供了一种更优雅的方式来隐式传递一个对象,
得益于this的机制,我们可以设计出更简洁并且易于复用的API;
那么,this到底又是什么呢?
this在运行时绑定,并不是在代码编写时绑定的,它的上下文取决于函数调用时的各种条件;
this的绑定与函数声明的位置没有任何关系,只取决于函数的调用方式;
当一个函数被调用时,会创建执行上下文这个记录会包含函数在哪被调用,调用的方式,传入的参数信息等。
this就是这个记录的一个属性,会在函数的执行的过程中用到
总而言之:this指的是谁,得看该函数调用位置的调用形式
this在不同方式调用this指向是不一样的,那么都有哪些调用方式呢?
一、独立调用(可以把这条规则看作是无法应用其他规则时的默认规则)
1. 在非严格模式下调用,this会被绑定给全局对象window
2. 函数如果在严格模式下执行,this会绑定为undefined
3. 如果只是在严格模式下调用,this绑定给的还是window
对应以上三种情况时,上代码看看都是什么情况:
/*1. 在非严格模式下调用,this会被绑定给全局对象window*/
function test(){
console.log(this); // window
}
test();
/*2.函数如果在严格模式下执行,this会绑定为undefined*/
function test(){
"use strict"
console.log(this); // undefined
}
test();
/*3. 如果只是在严格模式下调用,this绑定给的还是window*/
function test(){
console.log(this); // window
}
(function(){
"use strict"
test();
})()
二、隐式调用(this使用隐式绑定规则 绑给离他最近的调用者)
隐式绑定的规则是调用位置是否有上下文对象,
或者说是否被某个对象拥有或者包含,当函数引用有上下文对象时,
隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象;
这句话用代码就是这么个意思如下:
function foo() {
console.log( this.a ); // 这个this就是obj这个对象所以输出的是 2
}
var obj = {
a: 2,
foo: foo
};
obj.foo();
隐式调用函数有可能造成一个很大隐患,那就是隐式丢失;隐式丢失特别容易在变量赋值,传参,定时器 回调时发生;那么什么是隐式丢失呢?在三种情况下会造成隐式丢失
1. 变量赋值
function foo() {
console.log( this.a ); // oops, global
}
var a = "oops, global";
var obj = {
a: 2,
foo: foo
};
//隐式丢失
/*
obj.foo本来是想把this绑定给obj的,
但是在将obj中的foo方法赋值给变量bar,这时候隐式丢失就发生了
此时bar调用形式是独立调用,this绑定给了window
*/
var bar = obj.foo;
bar();
2. 传参数
// 参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值
function foo() {
console.log( this.a ); // "oops, global"
}
function doFoo(fn) {
fn(); // 独立调用,该函数的this绑定给window
}
var a = "oops, global";
var obj = {
a: 2,
foo: foo
};
//隐式丢失
doFoo( obj.foo );
3. 定时器回调
如果把函数传入语言内置的函数而不是传入你自己声明的函数,结果是一样的,没有区别
// JavaScript环境中内置的 setTimeout() 函数实现和下面的伪代码类似:
// function setTimeout(fn,delay) {
// // 等待delay毫秒
// fn(); // <-- 调用位置!
// }
function foo() {
console.log( this.a ); // "oops, global"
}
var a = "oops, global";
var obj = {
a: 2,
foo: foo
};
//隐式丢失
setTimeout( obj.foo, 1000 );
三、显示绑定(this使用显式绑定规则 this绑给call apply bind指定的对象)
我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数
具体点说,可以使用函数的 call(..) 和 apply(..) 方法来实现显示绑定
function foo(a,b) {
console.log( this.a,a,b );//装箱
}
var obj = {
a:2
};
foo.call(obj,"a","b"); // 2 a b
foo.apply(obj,["c","e"]) // 2 c e
call()和apply()之间有什么区别呢?从上面代码中可以看出,共同点第一个参数都是this指向,都是立即调用的;区别在call()中第二个参数是以列表的形式; apply()第二个参数是以数组的形式;
bind():方法中第一个参数也是表示this指向,与上面两种方法不一样的是,为函数调用该方法返回 的是一个函数,叫做“硬绑定函数”;不是立即调用的;调用时机由使用者决定;
function test(){
console.log(this);
}
//fn 我们 一般称之为test的硬绑定函数 此用法还解决了上面提到的隐式丢失问题
var fn = test.bind({name:"hhl"});
fn();
没错,bind()函数可以解决上面所提及的三种隐式丢失的问题;
四、new绑定
在js中实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”;
使用 new 来调用函数,或者说发生构造函数调用时,对于我们的this来说。
这个实例化的对象会绑定到函数调用的 this
function Foo(a) {
this.a = a; // this 指向实例化对象bar
}
var bar = new Foo(2);
console.log( bar.a ); // 2
如果四种调用形式同时存在或者说有一个以上调用形式存在时,this又会怎么绑定绑定给谁呢?所以以上四种调用形式当然有会有个优先级,结论就是new调用 > call apply bind 显示绑定 > 隐式绑定 > 独立调用
1. 显示绑定与隐式绑定哪个优先级高呢?
function foo() {
console.log( this.a );
}
var obj1 = {
a: 2,
foo: foo
};
var obj2 = {
a: 3,
foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2
如上代码所示,可得出 显示绑定 > 隐式绑定
2. new 绑定 与 隐式绑定比较
// new 绑定比隐式绑定优先级高
function foo(something) {
this.a = something;
}
var obj1 = {
foo: foo
};
//显>隐
//new>隐
var bar = new obj1.foo( 4 );
console.log( obj1.a ); // undefined
console.log( bar.a ); // 4
如上代码所示,可得出 new绑定 > 隐式绑定 3. new 绑定 与 显示绑定比较
// new绑定优先级高于显示绑定
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
var baz = new bar(3);
console.log( obj1.a ); // undefined
console.log( baz.a ); // 3
如上代码所示,可得出 new绑定 > 显式绑定
最后this绑定有三例特殊的情况:
1. es6中的箭头函数没有this,this指向外围函数
2. 如果你把null或者undefined作为this绑定的对象传入 call apply bind,这些值在调用时会被忽略,实际应用的是默认绑定规则
3. 柯里化:为函数预定一些参数