this用法总结

1.常规下this的指向

this关键字可以用在构造函数之中,表示实例对象。除此之外还可以用在别的场合。但是不管是什么场合,this都有一个共同点:它总是返回一份对象

1.1 全局环境中的this

函数在浏览器全局环境下被简单调用,在非严格模式下 this 指向 window,在通过 use strict 指明严格模式的情况下指向 undefined
例1:

function f1() {
    
    
    console.log(this);
}

function f2() {
    
    
    'use strict'
    console.log(this);
}

f1()     // window or global
f2()     // undefined

例2:

const foo = {
    
    
    bar : 10,
    fn : function(){
    
    
        console.log(this); 
        console.log(this.bar); 
    }
}
var fn1 = foo.fn;
fn1();

// window or global
// undefined

虽然fn函数在foo对象中作为该对象的一个方法,但是赋值给fn1之后,fn1仍然是在window的全局环境下执行的。因此this指向的还是window

例3:

const foo = {
    
    
    bar : 10,
    fn : function(){
    
    
        console.log(this); 
        console.log(this.bar); 
    }
}
foo.fn();   


// { bar: 10, fn: [Function: fn] }
// 10

这里,this指向调用它的对象,在foo.fn() 语句中,this指向的是foo对象

1.2 上下文对象调用中的this

一般通过上下文对象调用函数时,函数体内的 this 会被绑定到该对象上。
例4:

const student = {
    
    
    name: 'zhangsan',
    fn: function () {
    
    
        return this;
    }
}
console.log(student.fn() === student); 


// true

this 指向当前的对象student

在嵌套关系中,this指向最后调用它的对象
例5:

const student = {
    
    
    name: 'zhangsan',
    son: {
    
    
        name: 'zhangxiaosan',
        fn: function () {
    
    
            return this.name
        }
    }
}
console.log(student.son.fn()); 

// zhangxiaosan

高阶-例6:

const o1 = {
    
    
    text: 'o1',
    fn: function () {
    
    
        return this.text;
    }
}

const o2 = {
    
    
    text: 'o2',
    fn: function () {
    
    
        return o1.fn();
    }
}

const o3 = {
    
    
    text: 'o3',
    fn: function () {
    
    
        var fn = o1.fn;
        return fn();
    }
}

console.log(o1.fn()); 
console.log(o2.fn());
console.log(o3.fn());

 // o1
 // o1
// undefined

o1.fn() this指向调用它的对象,打印为o1;
o2.fn() this 指向为o1,打印为o1;
o3.fn() 这里是将o1.fn 赋值给fn,并return,所以在调用的时候,相当于全局调用function () { return this.text; },并不是以对象的形式调用,this指向window,所以打印为undefined

1.3 this指向绑定事件的元素

问题:在一个div节点的时间函数内部,有一个局部的callback方法,我们希望callback方法内部的this指向div节点

<div id="div1">我是一个div</div>


window.id = 'window';
document.getElementById('div1').onclick = function(){
    
    
  console.log('this1',this.id); 
  const callback = function(){
    
    
    console.log(this.id); 
  }
  callback();
}

在这里插入图片描述

由于callback作为普通函数被调用,所以this指向为window

解决: 通过变量保存的方式

window.id = 'window';
document.getElementById('div1').onclick = function(){
    
    
  console.log('this1',this.id); // div1
  const that = this; // 保存当前 this 的指向
  const callback = function(){
    
    
    console.log(that.id); 
  }
  callback();
}

在这里插入图片描述

1.4 箭头函数的this指向

箭头函数中的this 指向始终是指向的外层作用域(箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),此处指父级作用域)

var x = 20;
const obj = {
    
    
    x: 10,
    test: () => {
    
    
        console.log(this); // {}
        console.log(this.x); // undefined
    }
}
obj.test();

在这里插入图片描述

箭头函数的 this 指向与普通函数不一样,它的 this 指向始终是指向的外层作用域。所以这里的 this 实际上是指向的全局对象。

var name = "JavaScript";
const obj = {
    
    
    name: "PHP",
    test: function () {
    
    
        const i = function () {
    
    
            console.log(this.name);
            // i 是以函数的形式被调用的,所以 this 指向全局
            // 在浏览器环境中打印出 JavaScript,node 里面为 undeifned
        }
        i();
    }
}
obj.test(); // JavaScript

// 改为箭头函数:

var name = "JavaScript";
const obj = {
    
    
    name : "PHP",
    test : function(){
    
    
        const i = ()=>{
    
    
            console.log(this.name);
            // 由于 i 为一个箭头函数,所以 this 是指向外层的
            // 所以 this.name 将会打印出 PHP
        }
        i();
    }
}
obj.test();// PHP

另外箭头函数 不能作为构造函数

const Test = (name, age) => {
    
    
    this.name = name;
    this.age = age;
};
const test = new Test("xiejie", 18);
// TypeError: Test is not a constructor

2. 改变this指向

2.1 call - Function.prototype.call( )

call方法可以指定this指向(即函数执行时所在的作用域),然后在指定的作用域中执行函数。

// 函数.call(对象)
fun.call(thisArg, arg1, arg2, ...)
2.1.1 call的第一个参数

thisArg:在 fun函数运行时指定的 this值 。如果参数为空或 null、undefind,则默认传参全局对象,同时值为原始值(数字,字符串,布尔值)的 this会指向该原始值的自动包装对象。
例1:改变this指向

var obj = {
    
    };
var f = function(){
    
    
	return this;
};
console.log(f() === window);  
console.log(f.call(obj) === obj) 

执行f() 时,因为在全局 环境下执行,所以this指向window,通过call 传入第一个参数,call前面的函数执行时,this指向为第一个参数对象

例2:this指向传入undefined 、null

var n = 123;
var obj = {
    
     n: 456 };

function a() {
    
    
  console.log(this.n);
}

a.call() // 传入空,指向全局 ,123
a.call(null) //传入null 指向全局, 123
a.call(undefined) //传入undefined, 123
a.call(window) // 123
a.call(obj) //传入obj,this指向obj 456

例3: 传入Number类型,this指向包装对象

var f = function () {
    
    
  return this;
};

f.call(5); // Number {[[PrimitiveValue]]: 5}

在这里插入图片描述

2.1.2 call接受多个参数

第一个参数是 this 指向的对象,之后的是函数回调所需的参数
例4:

function add(a, b) {
    
    
  return a + b;
}

add.call(this, 1, 2) // 3
2.1.3 调用对象的原生方法

hasOwnProperty 该方法是查看一个对象是否有某一个属性或者方法
这个属性或者方法必须是自身就有的,而不是继承而来

var obj = {
    
    };
obj.hasOwnProperty('toString') // false

// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
    
    
  return true;
};
obj.hasOwnProperty('toString') // true

Object.prototype.hasOwnProperty.call(obj, 'toString') // false

上面代码中 hasOwnPropertyobj 继承来的方法,用来判断对象是否包含自身特点(非继承)属性,但是 hasOwnProperty 并不是保留字,如果被对象覆盖,会造成结果错误。

call 方法可以解决这个问题,它将 hasOwnProperty 方法的原始定义放到 obj 对象上执行,这样无论 obj 上有没有同名方法,都不会影响结果。

2.2 apply - Function.prototype.apply( )

func.apply(thisValue, [arg1, arg2, ...])
2.2.1 apply 与call 的区别

相同点:

  • apply与call的作用类似,也是改变this指向,然后调用函数
  • 第一个参数也是 this 所要指向的那个对象,如果设为 nullundefined,则等同于指定全局对象。

不同点:

  • apply接受数组作为 函数执行时的参数,在 call 方法中必须一个个添加,但是在 apply 方法中,必须以数组形式添加,该数组的所有成员依次作为参数,传入原函数。
function f(x, y){
    
    
  console.log(x + y);
}

f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2
2.2.2 利用apply传入数组的特性,实现一些小功能

1. 输出数组的最大值

var a = [24,30,2,33,1]
Math.max.apply(null,a)  //33

2. 将数组的空元素转化成undefined
意义:数组的 forEach 方法会跳过空元素,但是不会跳过 undefined。undefined可以通过forEach循环出来

var a = ['a', , 'b'];

function print(i) {
    
    
  console.log(i);
}

a.forEach(print)
// a
// b

Array.apply(null, a).forEach(print)
// a
// undefined
// b

3. 配合数组的slice方法,实现类数组转化为真正的数组

Array.prototype.slice.apply({
    
    0: 1, length: 1}) // [1]
Array.prototype.slice.apply({
    
    0: 1}) // []
Array.prototype.slice.apply({
    
    0: 1, length: 2}) // [1, undefined]
Array.prototype.slice.apply({
    
    length: 1}) // [undefined]

将类似数组的对象转化为数组

2.3 bind - Function.prototype.bind( )

f.bind(obj)

bind用于将函数体内的this绑定到某个对象,然后返回一个新函数:
例:
问题:

var d = new Date();
d.getTime() // 1481869925657

var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object.

执行print时,将getTime赋值给print,相当于全局调用,此时this指向window

使用bind解决:

var print = d.getTime.bind(d);
print() // 1481869925657

通过bind,返回一个新函数,这个新函数this被绑定到了d上

2.3.1 bind传入一个参数时

bind接收的参数就是所要绑定的对象
常规绑定:

var counter = {
    
    
  count: 0,
  inc: function () {
    
    
    this.count++;
  }
};

var func = counter.inc.bind(counter);
func();
counter.count // 1

bind中传入对象为counter,所以bind前函数执行时this指向counter

绑定到其他对象:

var counter = {
    
    
  count: 0,
  inc: function () {
    
    
    this.count++;
  }
};

var obj = {
    
    
  count: 100
};
var func = counter.inc.bind(obj);
func();
obj.count // 101

传入的对象为obj,所以执行bind前的函数时,函数指向obj

2.3.1 bind传入多个参数时

bind 接受多个参数时,除了第一个参数,其他参数会绑定到原函数的参数

var add = function (x, y) {
    
    
  return x * this.m + y * this.n;
}

var obj = {
    
    
  m: 2,
  n: 2
};

var newAdd = add.bind(obj, 5);
newAdd(5) // 20

bind的第一个参数为obj,所以将bind前函数的this指向为obj,后面的参数会作为调用add方法时的参数传入

2.3.2 第一个参数为null或undefined时

bind 方法的第一个参数是 nullundefined,等于将 this 绑定到全局对象,函数运行时 this 指向顶层对象(浏览器为 window)。

function add(x, y) {
    
    
  return x + y;
}

var plus5 = add.bind(null, 5);
plus5(10) // 15

函数add内没有使用this,bind方法主要目的是绑定x,y参数,每次运行plus5时,只需要传入另一个参数y就行了。

2.3.3 bind使用时的注意点(即:使用bind的几个场景)
2.3.3.1 bind每一次返回一个函数

事件监听时:
由于每次运行时,就会返回一个新函数。所以上面的代码click事件绑定bind方法会生成一个匿名函数。导致无法取消绑定

element.addEventListener('click', o.m.bind(o));

//取消绑定时
element.removeEventListener('click', o.m.bind(o));

解决:

var listener = o.m.bind(o);
element.addEventListener('click', listener);
//  ...
element.removeEventListener('click', listener);
2.3.3.2 将包含 this 的方法直接当作回调函数
var counter = {
    
    
  count: 0,
  inc: function () {
    
    
    'use strict';
    this.count++;
  }
};

function callIt(callback) {
    
    
  callback();
}
// 写法1: callIt(counter.inc())
callIt(counter.inc.bind(counter));
counter.count // 1

使用如上写法1 的方式传入函数,调用时,this会指向window,相当于全局调用。解决方式就是通过bind绑定this

追问:某些数组方法接收的函数中的this指向

var obj = {
    
    
  name: '张三',
  times: [1, 2, 3],
  print: function () {
    
    
    this.times.forEach(function (n) {
    
    
      console.log(this.name);
    });
  }
};

obj.print()

如上代码没有任何输出,因为this指向为window

解决:

obj.print = function () {
    
    
  this.times.forEach(function (n) {
    
    
    console.log(this.name);
  }.bind(this));
};

obj.print()
// 张三
// 张三
// 张三
*2.3.3.3 结合call的使用

利用 bind 方法,可以改写一些 JavaScript 原生方法的使用形式,以数组的 slice 方法为例。

[1, 2, 3].slice(0, 1) // [1]
// 等同于
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]

这样做的本质是在 [1, 2, 3] 上面调用 Array.prototype.slice 方法,因此可以用 call 方法表达这个过程,得到同样的结果。
call 方法实质上是调用 Function.prototype.call 方法,因此上面的表达式可以用 bind 方法改写。

var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 0, 1) // [1]

上面代码的含义就是,将 Array.prototype.slice 变成 Function.prototype.call 方法所在的对象,调用时就变成了 Array.prototype.slice.call。类似的写法还可以用于其他数组方法。

var push = Function.prototype.call.bind(Array.prototype.push);
var pop = Function.prototype.call.bind(Array.prototype.pop);

var a = [1 ,2 ,3];
push(a, 4)
a // [1, 2, 3, 4]

pop(a)
a // [1, 2, 3]

如果再进一步,将 Function.prototype.call 方法绑定到 Function.prototype.bind 对象,就意味着 bind 的调用形式也可以被改写。

function f() {
    
    
  console.log(this.v);
}

var o = {
    
     v: 123 };
var bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f, o)() // 123

上面代码的含义就是,将 Function.prototype.bind 方法绑定在 Function.prototype.call 上面,所以 bind 方法就可以直接使用,不需要在函数实例上使用。

2.4 手写call、bind、apply

  • 原理或者手写类题目,结题思路
    1. 说明原理,写下注释
    1. 根据注释,补齐代码

手写bind

// 1. 需求:手写bind => bind位置(挂在那里) => Function.prototype
    Function.prototype.newBind = function() {
    
    
        // 2. bind是什么? 
        const _this = this;
        const args = Array.prototype.slice.call(arguments);
        // args特点,第一项是新的this,第二项~最后一项函数传参
        const newThis = args.shift();

        // a. 返回一个函数
        return function() {
    
    
            // b. 返回原函数执行结果 c. 传参不变
            return _this.apply(newThis, args);
        }
    }

手写apply

 Function.prototype.newApply = function(context) {
    
    
        // 边缘检测
        // 函数检测
        if (typeof this !== 'function') {
    
    
            throw new TypeError('Error');
        }
        // 参数检测
        context = context || window;

        // 挂载执行函数
        context.fn = this;

        // 执行执行函数
        let result = arguments[1]
            ? context.fn(...arguments[1])
            : context.fn();

        // 销毁临时挂载
        delete context.fn;
        return result;
    }

3.总结

this 的指向哪几种 ?
总结起来,this 的指向规律有如下几条:

  • 在函数体中,非显式或隐式地简单调用函数时,在严格模式下,函数内的 this 会被绑定到 undefined 上,在非严格模式下则会被绑定到全局对象 window/global 上。
  • 一般使用 new 方法调用构造函数时,构造函数内的 this 会被绑定到新创建的对象上。
  • 一般通过 call/apply/bind 方法显式调用函数时,函数体内的 this 会被绑定到指定参数的对象上。
  • 一般通过上下文对象调用函数时,函数体内的 this 会被绑定到该对象上。
  • 在箭头函数中,this 的指向是由外层(函数或全局)作用域来决定的。

猜你喜欢

转载自blog.csdn.net/weixin_44247866/article/details/127903214
今日推荐