js中this相关内容

228 阅读5分钟

为什么需要this?

this代表的是当前调用对象,指向当前函数的调用者,并不是函数的本身。在开发过程中,如果没有this,也是能够进行开发的,也有很多种解决方案,但是没有this会让我们代码编写非常不方便,this可以隐式的传递一个对象的引用,进行及其灵活的调用,极大的提高开发效率。

例如: 当我们创建一个对象的时候,内部存在eating和study方法,我们分别使用this和obj来获取当前对象的name属性,都是可以获取到的,当我们需要创建多个相同对象的时候,我们cv的时候,obj对象改变成test对象,那么study方法中name的调用者也就变成了test,需要改变 (只是简单举个例子)

let obj ={
    name: 'qqq',
    eating: function(){
        console.log(this.name, '吃吃吃')
    }
     study: function(){ // 不使用this
        console.log(obj.name, '吃吃吃')
    }
}

this的指向

this在全局作用域下指向 浏览器指向window node环境中指向{}

this指向什么和函数所处在的位置是没有关系的,函数在调用的时候,js会给this绑定一个默认的值,跟函数被调用的方式以及调用位置有关。(this是在运行的时候才被绑定的)

this的绑定规则

  1. 默认绑定
  2. 隐式绑定
  3. 显示绑定
  4. new绑定

默认绑定

当我们这个函数是被独立调用的时候,该函数就是默认绑定

例:

function test(){
    console.log(this)
}
test()
// 独立调用,默认绑定,在浏览器下指向window
​
​
function done(){
    console.long(this)
    test()
}
done()
// 输出为两个window, 我们不看函数所处位置,看函数调用方式和调用位置,test还是独立调用的
// 独立调用,默认绑定,在浏览器下指向window

隐式绑定

是通过某个对象来进行调用的,调用的位置存在上下文对象。

例:

let name = 'window'
function tt(){
  console.log(this)
}
var obj = {
    name: 'qqq',
    test: tt,
    demo: function () {
        console.log(this.name) //qqq
    },
     sayHi: function(){
        setTimeout(function(){
            console.log('Hello,',this);
        })
    }
}
​
obj.test()
// 该test函数是通过obj来调用的,那么他绑定的this就是obj对象
obj.sayHi() 

若函数呗setTimeout等包裹,在通过obj调用,setTimeout中this指向window(先不考虑箭头函数的影响),如本例子中,obj调用sayHi,该this指向window,我们可以把setTimeout中的function理解为一个callback函数,如下述代码,我们看这个fn(),就相当于是独立函数调用,所以指向window

function callback(){}
setTimeout(callback, time)// setTimeout中传入了callback函数
// setTimeout执行
function setTimeout(fn, delay) {
    fn()
}
setTimeout()

显示绑定

显示绑定,就是通过call,apply,bind的方式,显示的改变this的指向方式。

call() 和 apply() 能够传入第一个参数来改变this 的指向,第二个参数:call和apply两者传参不同,一者是一个个字符(以剩余参数的形式),一者是数组传入参数。

如果我们将null或者是undefined作为this的绑定对象传入call、apply或者是bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。

例: call/apply

function demo() {
    console.log('调用函数',this)
}
// foo() // 直接调用,指向全局对象,即window
// foo.call(); foo.apply(); // 这样可以直接调用,不绑定this, 默认是window
// call() 和 apply() 能够传入一个参数来改变this 的指向
demo.call('call')
demo.apply('apply')

例: bind

function demo() {
    console.log('调用函数',this)
}
​
var newFn = demo.bind('new')
​
// 该处,可能会以为这里显示绑定与默认绑定冲突了,但是显示绑定优先级更高,以显示绑定为主
newFn() // this指向new

new绑定

会帮我们重新创建一个新的对象,构造函数中的this,指向这个空的对象,这个新的对象被执行,执行构造函数和方法,属性和方法被添加到this引用的对象中。

function _new() {
    let target = {}; //创建的新对象
    //第一个参数是构造函数
    let [constructor, ...args] = [...arguments];
    //执行[[原型]]连接;target 是 constructor 的实例
    target.__proto__ = constructor.prototype;
    //执行构造函数,将属性或方法添加到创建的空对象上
    let result = constructor.apply(target, args);
    if (result && (typeof (result) == "object" || typeof (result) == "function")) {
        //如果构造函数执行的结构返回的是一个对象,那么返回这个对象
        return result;
    }
    //如果构造函数返回的不是一个对象,返回创建的新对象
    return target;
}
​

例: new绑定,我们通过new关键字调用一个函数(构造函数)的时候,这时候的this是在调用这个函数创建出来的对象。

function Person(name, age) {
    this.name = name
    this.age = age
}
var p1 = new Person('qq', 1)
console.log(p1.name, p1.age) //'qq' ,1

关于内置函数中this绑定

// 1、setInterval()
function callback(){}
setInterval(callback, time)// setInterval
// setTimeout执行
function setInterval(fn, delay) {
    fn() // window
}
setTimeout()
// 2、监听点击事件的时候
const box = document.querySelector('.box')
box.onclick = function(){
    // 内部做了 box.onclick() 通过隐式绑定来执行的
    console.log(this) // 返回给我们该元素的对象
}
// 3、数组foreach、map等引用的时候
var demo = [1,2,3,4]
demo.forEach(function() {
    // 是通过内部的回调来执行的
    console.log(this) // window
}, 'xxx'可以改变this的指向)

绑定的优先级

new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

默认绑定优先级一定是最低的。存在其他规定的时候,默认优先级一定会被覆盖的

// demo 显示高于隐式
var obj = {
    name :'213',
    foo: function(){
        console.log(this)
    }
}
obj.foo.apply('asd') // asd
// 输出为asd,显示高于隐式let test = new obj.foo()
test() // foo的函数对象,new高于隐式// 显示与new相比较
// new 和 apply call不能一起使用,只能和bind相比较
function tt() {
    console.log(this)
}
var bar = tt.bind('123')
​
var o = new bar()
o() // 打印函数对象,new 高于显示 

箭头函数

箭头函数是ES6中新增的,他和普通函数有一些区别,箭头函数不会绑定this,arguments属性,箭头函数也不能当作构造函数来使用。

箭头函数体内的this,是继承外层代码块的this。

var demo = {
    frist: function(){
        console.log('frist', this)
        return () =>{
            console.log('frist-2', this)
        }
    },
    second: () => {
        // 指向上一层的this,该处上一层是window,作用域只有函数作用域和全局作用域
        console.log(this)
    }
}
demo.frist()  // demo
demo.frist()() // demo
demo.second() // window

高阶函数中使用箭头函数

let arr = [1,2,3,4,5]
arr.forEach((item)=>{
    console.log(item, this) // this = {}
})

附:面试题一

var name = "window";
​
function Person(name) {
  this.name = name;
  this.foo1 = function () {
    console.log(this.name);
  };
  this.foo2 = () => {
    console.log(this.name);
  };
  this.foo3 = function () {
    return function () {
      console.log(this.name);
    };
  };
  this.foo4 = function () {
    return () => {
      console.log(this.name);
    };
  };
}
​
var person1 = new Person("person1");
var person2 = new Person("person2");
​
person1.foo1(); //perosn1
person1.foo1.call(person2); //person2
​
person1.foo2(); //perosn1
person1.foo2.call(person2); //person1
​
person1.foo3(); //window
person1.foo3.call(person2)(); //window
person1.foo3().call(person2); //person2
​
person1.foo4()(); //perosn1
person1.foo4.call(person2)(); //person2
person1.foo4().call(person2); //perosn1

\