更通俗的理解js中this指向问题

97 阅读7分钟

关于this

  1. thisJavaScript 的一个关键字,一般指向调用它的对象

    1.1. this 指向的应该是一个对象,更具体地说是函数执行的上下文对象

    1.2. 如果调用它的不是对象或对象不存在,则会指向全局对象(严格模式下为 undefined)

  2. this的值是在执行的时候才能确认,定义的时候不能确认,像dom 绑定事件来说,最后执行dom的绑定

this 指向四种情况

总体来说this 绑定形式分为四种,默认绑定隐式绑定显示绑定new绑定

  1. 默认绑定函数没有被绑定到某个对象上进行调用,这类调用指向window,可以理解成是window调用这些函数
function a() {
	console.log(this)
}
a()
  1. 隐式绑定:是通过某个对象发起的函数调用,这类this 指向往往是发起对象
const a = {
	fun(){
		console.log(this)  // a
	}
}
a.fun()
  1. 显示绑定:使用call和apply方法,间接的将this绑定到了这个对象上,这类操作们明确的绑定了this指向的对象
const a = {}
function b() {
	console.log(this) // a
}
b.call(a)

4.new绑定:这里this指向是其返回新函数(注:前提构造函数没有返回其他对象)

  • 注:总体来说常见日常使用都是遵循谁调用指向谁的规则

call、apply 的指向

1.call、apply 和 bind 是挂在 Function 对象上的三个方法,调用这三个方法的必须是一个函数,三者区别bind 虽然改变了 func 的 this 指向,但不马上执行,'call、apply'是在改变了函数的 this 指向之后立马执行

func.call(thisArg, param1, param2, ...)

func.apply(thisArg, [param1,param2,...])

func.bind(thisArg, param1, param2, ...)

如果是非严格模式'thisArg 为null/undefined' 指向是window,基本类型指向其包装对象,严格模式'null/undefined' 指向是undefined,基本类型为本身

2.使用它们仅仅是改变this这么简单么?现在来想举个例子忽然你只是一下想玩游戏机了,但是你没有,你去 借了一台玩完了 换了回去,首先游戏机你是临时想玩,如果去买一个以后大概率吃灰了,借一下你却体验了 游戏的感觉还没有花钱,这个白嫖的过程带入'call' 'apply' 'bind' 来看,有些方法我对象没有但是我只是忽然间用一下我向你白嫖一下'既达到了目的,又节省重复定义,节约内存空间'

  • 实现
1.首先call 和 apply 只有方法才能调用,因此需要让每个方法都要具备这两个功能
2.指向对象 要有借这个动作,并且借完之后就销毁(理解成游戏机还回去了)

实现call

Function.prototype.newCall = function(context,...args){
    // this 谁调用指向谁,此时是function

    context.fn = this
    var context = context || window;
     // 我要借你的function 方法,这里fn 不是固定的
    // 相当于context 对象 借了一个叫fn 先存起来
    context.fn = this;
    // 我使用一下 到了我玩一下这个 你也可以直接context.fn(...args) 使用
    var result = eval('context.fn(...args)');
    // 用完了还给你
    delete context.fn
    return result;
}
// apply 同理
Function.prototype.newApply = function (context, args) {
  let context = context || window;
  context.fn = this;
  let result = eval('context.fn(...args)');
  delete context.fn
  return result;
}

实现一个bind

1.call 和 apply 与bind 的区别是前者立即执行,后者是返回函数,但是二者本质都是改变
this指向,这其实利用闭包思考,我返回的一个函数但是函数内部通过call 执行
 function bind(context, ...params) {
        let self = this;
        return function proxy(...args) {
            params = params.concat(args);
            return self.call(context, ...params);
        };
    };
  • 更更多情况思考案例
Function.prototype.newBind = function (context, ...args) {
    if (typeof this !== "function") {
      throw new Error("this must be a function");
    }
    var self = this;
    // 本质上来思考 bind是和 apply 和 call 很类似,区别是bind返回的是function
    // apply 和 call 是直接执行 因此本质上借用bind借用apply但是包裹在function
    // 中不让其执行并且返回function
    var fbound = function () {
        self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
    }
    if(this.prototype) {
      fbound.prototype = Object.create(this.prototype);
    }
    return fbound;
}

案例说明

  • 案例 一
  1. 下面函数fn 是通过对象'o'调用的,因此根据上面'this一般指向'调用它的对象'那么此时的this 就可以 很好的理解为什么打印的是'o'

// 代码 1

var o = {

fn() {

console.log(this)

}

}

o.fn() // o

  • 案例二
  1. 代码2和代码1同理,不同点是 a 是通过类A 创建的,但是方法的调用是a调用的,因此this 是a

// 代码 2

class A {

fn() {

console.log(this)

}

}

var a = new A()

a.fn()// a

  • 案例三

1.下面fn 由于没有找到调用 fn 的对象,所以 this 会指向全局对象 指向是window


// 代码 3

function fn() {

console.log(this)

}

fn() // 浏览器:Window;Node.js:global

  • 案例四
  1. 虽然现在的fn 是在fn2内部执行,但是fn 由于没有找到调用 fn 的对象,所以 this 会指向全局对象 指向是window

function fn() {console.log(this)}

function fn2() {fn()}

fn2() // Window;Node.js:global

  • 案例五
  1. 虽然fn 是在函数fn2 中执行,并且fn2是对象obj 调用,fn2 的this 指向是obj,但是fn2()中的 this 指向并不会传递给函数 fn(),因此fn也是window

function fn() {console.log(this)}

function fn2() {fn()}

var obj = {fn2}

obj.fn2() // Window;Node.js:global

  • 案例六
  1. fn 由于没有找到调用 fn 的对象,所以 this 会指向全局对象指向是window,但是这里要注意一点 ES6 下的 class 内部默认采用的是严格模式,严格模式下不在是window 而是 undefined

class B {

fn() {

console.log(this)

}

}

var b = new B()

var fun = b.fn

fun() // undefined

es6特殊箭头函数

1.不绑定 arguments 对象,也就是说在箭头函数内访问 arguments 对象会报错;
2.不能用作构造器,也就是说不能通过关键字 new 来创建实例;
3.默认不会创建 prototype 原型属性;
4.不能用作 Generator() 函数,不能使用 yeild 关键字。
5.'this' 指向是由最外层最近的一层非箭头函数
  • 案例一
  1. 'this' 指向是由最外层最近的一层非箭头函数,当然你也需要看着最外层非箭头函数的函数this指向 是谁,那么对应的箭头函数this指向就是谁
  2. 下面的getName 箭头函数虽然是a调用的但是要明确,箭头函数是看最近的非箭头函数的指向, 下面的getName 不存在被非箭头函数包裹,因此它的this 就是window
const a = {
    name:'w',
    getName:()=>{
        console.log(this) 
    }
}
a.getName()  // window
  • 案例二
  1. 箭头t 最外层非箭头函数是getName,此时getName 是 a 调用,所以箭头函数就是this 就是a
const a = {
        name:'w',
        getName(){
            const t = ()=>{console.log(this)}
            t()
        }
    }
    a.getName() // a
  • 案例三 mdn的案例
// 创建一个含有bar方法的obj对象,
// bar返回一个函数,
// 这个函数返回this,
// 这个返回的函数是以箭头函数创建的,
// 所以它的this被永久绑定到了它外层函数的this。
// bar的值可以在调用中设置,这反过来又设置了返回函数的值。
var obj = {
  bar: function() {
    var x = (() => this);
    return x;
  }
};

// 作为obj对象的一个方法来调用bar,把它的this绑定到obj。
// 将返回的函数的引用赋值给fn。
var fn = obj.bar();

// 直接调用fn而不设置this,
// 通常(即不使用箭头函数的情况)默认为全局对象
// 若在严格模式则为undefined
console.log(fn() === obj); // true

// 但是注意,如果你只是引用obj的方法,
// 而没有调用它
var fn2 = obj.bar;
// 那么调用箭头函数后,this指向window,因为它从 bar 继承了this。
console.log(fn2()() == window); // true

优先级

默认绑定 < 隐式绑定 < 显示绑定 <new绑定


// function foo() {

// console.log("foo:", this)

// }

// 比较优先级:

// 1.显式绑定绑定的优先级高于隐式绑定

// 1.1.测试一:apply高于默认绑定

// var obj = { foo: foo }

// obj.foo.apply("abc")

// obj.foo.call("abc")

// 1.2.测试二:bind高于默认绑定

// var bar = foo.bind("aaa")

// var obj = {

// name: "why",

// baz: bar

// }

// obj.baz()

// 2.new绑定优先级高于隐式绑定

// var obj = {

// name: "why",

// foo: function() {

// console.log("foo:", this)

// console.log("foo:", this === obj)

// }

// }

// new obj.foo()

// 3.new/显式

// 3.1. new不可以和apply/call一起使用

// 3.2. new优先级高于bind

// function foo() {

// console.log("foo:", this)

// }

// var bindFn = foo.bind("aaa")

// new bindFn()

// 4.bind/apply优先级

// bind优先级高于apply/call

function foo() {

console.log("foo:", this)

}

var bindFn = foo.bind("aaa")

bindFn.call("bbb")

总结

  1. this的指向主要分为五种,抛开(with 和 eval),其中有四种属于 在'es5' 中就存在的,另一种是'es6'中才有的

    1.1.作为对象的方法调用( 指向'对象')

    1.2.作为普通函数调用(指向全局对象Window

    1.3. 构造器调用(构造函数中的this 指向是返回的对象

    1.4. 'Function.prototype.call' 或 'Function.prototype.apply' 调用

    1.5. 箭头函数的'this'调用(es6) ('this' 指向是由最外层最近的一层非箭头函数