本文参考了https://segmentfault.com/a/1190000009393621、https://blog.csdn.net/joyvonlee/article/details/94360946两篇文章,大家也都可以去看看
一、调用位置
如何查看函数调用的位置
可以使用
debugger
来查看函数的调用栈以及this
指向
function foo(a){
debugger
console.log(this,a);
}
foo(1);
var F=foo(2);
var F1=new foo(3);
而
二、this
四种绑定规则
1、默认绑定
默认绑定指函数没有任何引用进行调用的情况下运行
function test(){
console.log(this.a,this.b);
}
let b=0;
var a = 1;
test();
test()
函数是直接运行的,调用位置的作用域是全局作用域,所以this
指向了window
,所以this.a=1
,
但是…this.b=undefined
??? 这里需要注意,这是一个坑!!!,对于关键字let
,此刻变量依然是全局变量,作用于全局,但是不再作为全局对象的属性存在了,这里需要和var
区分一下!
这里附一道面试题~~~
let length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function(fn) {
fn();
arguments[0]();
}
}
obj.method(fn, 1);//0,2
首先来看代码执行,obj.method(fn, 1);
obj
对象里边的method
是一个函数,它里边的fn()
执行的时候,应用了this
默认绑定规则,所以this
指向window
,window.length
是等于0(length
对象使用let
关键字声明的,它不属于全局window
对象的一个属性,所以window.length
和它(let length = 10)
是两个不同的对象。这是一个坑!),所以执行这行语句的时候会输出 0 ,接下来是执行arguments[0]()
; arguments
对象是一个类数组,它的第0位下标是实参列表的第1个参数,也就是fn
函数。
当这个fn
函数调用的时候,它的this
被绑定到arguments
对象上。因为obj.method
传入了两个参数,所以arguments
对象的length
属性为2
2、隐式绑定
隐式绑定指一个函数,被当作引用属性添加到了一个对象中,然后以 “
对象名.函数名()
” 形式进行调用,这时如果函数引用有上下文对象,隐式绑定规则会把函数调用中的this
绑定到这个上下文对象。
function test(){
console.log(this.a);
}
var obj = {
a:3,
test:test
};
obj.test();//3
隐式绑定----链式调用
对象属性引用链中,只有上一层或者说最后一层在调用位置中起作用
function test(){
console.log(this.a);
}
var obj2 = {
a:4,
test:test
};
var obj1 = {
a:400,
obj2:obj2
}
obj1.obj2.test()//4
隐式绑定----隐式丢失
原本应用隐式绑定的,因为丢失绑定对象,变回应用默认绑定了!把
this
绑定到全局对象或undefined
。
当对象将其引用属性给了新的引用,再次调用这个新的引用时,原本this
指向的对象就会改为指向window
。
function foo(){
console.log(this.a);
}
var obj = {
a:5,
foo:foo
};
var bar = obj.foo; //这句就是关键 bar是obj的引用属性foo
var a = "oops, global";
bar(); // "oops, global"
bar
引用obj
的foo
属性,即foo()
函数, var bar = obj.foo;
就等价于 var bar=foo();
,当bar
函数调用的时候,由于是在全局作用域下调用,所以它的this
指向了全局window
,所以最后输出了"oops, global"
3、显式调用
如果不想在对象内部包含函数引用,而想在某个对象强制调用一个函数,这时就用到了函数的
call()
和apply()
方法。
function foo() {
console.log(this.a)
}
var obj = {
a: 10
}
foo(); // undefined
foo.call(obj); // 10
call
和apply
是一样的,都是将foo
的this
指向obj
,再去指向foo
的方法,区别在于apply
的第二个参数是arguments
对象或者一个数组,call
可以传递多个参数
显式调用----硬绑定
bar
函数内部将foo
的this
绑定到obj
对象上,之后无论如何调用bar
函数,foo
的this
总是指向到obj
对象的,这种绑定是一种显式的强制绑定。
第一种方式
function foo() {
console.log(this.a)
}
var obj = {
a: 10,
foo: foo
}
var bar = function() {
foo.call(obj);
}
var test = bar;
test(); // 10
test.call(window); // 10
第二种方式
硬绑定----bind()
绑定
function foo(arg) {
console.log(this.a, arg)
return this.a + arg;
}
// 辅助函数
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments)
}
}
var obj = {
a: 2
}
var bar = bind(foo, obj);
var b = bar(3); // 2 3
console.log(b) // 5
这种实现方式就是ES5
的内置方法Function.prototype.bind
的实现原理,下面是内置函数bind
的用法
function foo(arg) {
console.log(this.a, arg)
return this.a + arg;
}
var obj = {
a: 2
}
var bar = foo.bind(obj);
var b = bar(3); // 2 3
console.log(b) // 5
硬绑定bind()延迟调用
function test(){
console.log(this.a);
}
var obj2 = {
a:4,
test:test
};
var obj1 = {
a:400,
obj2:obj2
}
var bar=obj2.test.bind(obj2)//4
bar();
var bar1=obj2.test.call(obj2)//报错
bar1();
var bar2=obj2.test.apply(obj2)//报错
bar2();
为什么后两个会报错?
因为call和apply都是绑定后立刻执行的,都执行完了,bar就只是一个没有赋值的变量而已
关于apply、call、bind
共同点:
1、都用于控制this
指向;
2、第一个参数都是this
需要指向的对象,也就是上下文;
3、都可以后续参数传递;
4、没有任何参数时,this都指向全局对象window
区别:
1、call
、apply
绑定后立刻执行,bind
是延迟执行。换言之,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,就使用bind()
方法
4、new
绑定
使用
new
来调用foo()
时,会构造一个新对象,并把它绑定到foo()
调用中的this上
function foo() {
this.f1 = 'test';
}
var b = new foo();
console.log(b.f1); // test