入坑----关于this调用位置和指向问题

本文参考了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指向windowwindow.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引用objfoo属性,即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

callapply是一样的,都是将foothis指向obj,再去指向foo的方法,区别在于apply的第二个参数是arguments对象或者一个数组,call可以传递多个参数

显式调用----硬绑定

bar函数内部将foothis绑定到obj对象上,之后无论如何调用bar函数,foothis总是指向到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、callapply绑定后立刻执行,bind是延迟执行。换言之,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,就使用bind()方法

4、new绑定

使用new来调用foo()时,会构造一个新对象,并把它绑定到foo()调用中的this上


function foo() {
    this.f1 = 'test';
}
 
var b = new foo();
console.log(b.f1);  // test

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章