THIS专题汇总

131 阅读3分钟

JS中THIS的五种情况梳理

事件绑定

函数执行(包括自执行函数)

new构造函数

箭头函数

call/apply/bind

1、匿名函数里面的this在非严格模式下是window,在严格模式下是undefined

let obj = {
    x: 0,
    fn() {
        setTimeout(function () {
            // this -> window
            this.x++;
            console.log(obj.x);
        }, 1000);
    }
};
obj.fn(); 

2、我们把this存在self里面

let obj = {
    x: 0,
    fn() {
        // this -> obj
        let self = this;
        setTimeout(function () {
            // this -> window
            self.x++;
            console.log(obj.x);
        }, 1000);
    }
};
obj.fn(); 

3、箭头函数里面的this指的是当前上下文中的this

let obj = {
    x: 0,
    fn() {
        //这里面的this指的是fn这个函数的执行上下文中的this
        setTimeout(() => {
            this.x++;
            console.log(obj.x);
        }, 1000);
    }
};
obj.fn(); 

4、window

let obj = {
    x: 0,
    fn: () => {
        // this->window
        console.log(this);
    }
};
obj.fn(); 

5、直接执行一个函数,里面的this指的是window

6、将函数放在对象里面,那么让这个函数执行,this指向的是这个对象

7、call执行都做什么

 fn首先基于__proto__找到Function.prototype上的call方法,把call方法执行
   + 传递的实参 obj
   + call方法中的this -> fn
 call方法执行的作用是:把fn「this」执行,并且让方法fn「this」中的this指向变为第一个传递的实参「obj」 

  function fn(x, y) {
      console.log(this, x, y);
      return x + y;
  }
  let obj = {
      name: 'obj'
  }; 

  fn.call(obj)
  // fn.call(obj, 10, 20); //this->obj x->10 y->20
  // fn.call(10, 20); //this->10 x->20 y->undefined
  // fn.call(); //->非严格模式下:this->window「传递第一个参数是null/undefined也是window」  严格模式下:this->undefined「传递的第一个参数是谁,this就是谁」
  


8、apply和call的区别

 apply和call只有一个区别:传递给执行函数的实参方式不一样
   fn.call([context],params1,params2,...)
   fn.apply([context],[params1,params2,...])
最后结果都是把params一项项的传递给fn的

// let arr = [10, 20];
// fn.call(obj, arr); //->this:obj x:arr y:undefined
// fn.apply(obj, arr); //->this:obj x:10 y:20
// fn.call(obj, ...arr); //->fn.call(obj, 10, 20)  //->this:obj x:10 y:20

总结:call的性能要比apply好一丢丢「尤其是传递的实参在三个以上」

9、应用一:排序数组 (1)sort

(2)Math.max()传入的是一堆数,不是一个数组

(3)这样可以

console.log(Math.max.apply(Math, arr)); //->23

(4)假设法

// let max = arr[0];
// arr.slice(1).forEach(item => {
//     if (item > max) {
//         max = item;
//     }
// });
// console.log(max);

(5)自己拼字符串

// let str = 'Math.max(' + arr + ')'; //->'Math.max(1,5,6,23,14,15)'
// console.log(eval(str)); //->23

10、应用二:鸭子类型(长得像鸭子,称他为鸭子,最主要的是想要让他具备鸭子的特点)

11、arguments.proto===Object.prototype 类数组对象,不能直接使用数组的方法;类数组的原型指向的是Object.prototype 而普通的数组指向的是Array.prototype

12、把类数组转换为数组的方法

(1) let arr = [...arguments];

(2) let arr = Array.from(arguments);

(3) let arr = Array.prototype.slice.call(arguments, 0) <===> let arr = [].slice.call(arguments) 13、数组求和

(1)先转化为数组,再用数组的方法

 function fn() {
    var arr = [].slice.call(arguments)
    return arr.reduce((total,item)=>total+item)
 }

(2)直接借用

 function fn() {
     return [].reduce.call(arguments,(total,item)=>total+item)
 }

(3)改变原型

 function fn() {
     arguments.__proto__ = Array.prototype;
     return arguments.reduce((total, item) => total + item); 
 }

14、更暴力的办法:直接把你的东西抢过来用

//模拟array的实现

Array.prototype.push = function (val) {
    // this -> 数组
    // 1.把val放置在数组的末尾
    // this[this.length]=val;
    // 2.数组长度累加
    // this.length++;
    // return this.length;
};
arr.push(10); 


//类数组调用push
let obj = {
    2: 3, //1
    3: 4, //2
    length: 2, //3 4
    push: Array.prototype.push
};
obj.push(1); //-> obj[2]=1  obj.length++
obj.push(2); //-> obj[3]=2  obj.length++
console.log(obj); //=>{2:1,3:2,length:4} 

15、bind

call/apply都是立即把函数执行「改变THIS和传递参数」 
bind没有把函数立即执行,只是把后期要改变的this及传递的参数预先存储起来「柯理化」

function fn(x, y, ev) {
  console.log(this, x, y, ev);
  return x + y;
}
let obj = {
    name: 'obj'
};
// document.onclick = fn; //->点击文档才执行fn  this->document  x->MouseEvent事件对象  y->undefined
// document.onclick = fn.call(obj, 10, 20); //->立即执行了fn,我们需要点击的时候才执行
// document.onclick = function (ev) {
//     // this->document
//     fn.call(obj, 10, 20, ev);
// };

document.onclick = fn.bind(obj, 10, 20);

/* 
// 执行bind,fn没有立即执行「预先把fn/obj/10/20都存储起来了」,返回一个新函数
let proxy = fn.bind(obj, 10, 20);
// 执行返回的函数,proxy内部帮助我们把fn执行「this和参数该处理都处理了」
proxy(); 
*/