Javascript 中的 this

226 阅读3分钟

函数运行时候,函数内部自动生成的一个对象,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象

1.当为函数调用时

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

从代码中可以看出这个时候 this 指向window,  因为 fn 挂载在 window 下,所以执行fn的是window ,所以this 是window

2.当为对象方法调用时候

当做对象方法调用时候,this 指向这个对象,所以还是有函数执行的时候才能确定this到底指向谁

function fn() {
    console.log(this) // obj
}
var obj = {
    test: fn
}
obj.test()

我们再来看一个例子

function fn() {
    console.log(this) // obj
}
var obj = {
    test: fn
}
var fn2 = obj.test
fn2()

fn2 的调用对象是Window ,虽然fn2 的方法是 obj.test, 但是执行者是window

3. 当作为构造函数使用时候

function Fn() {
    console.log(this)  // a  
}
var a = new Fn()

这里可以看做 new 关键字,改变了this的指向, new之后都发生了什么呢,可看做以下三个步骤

  • 创建一个空对象,将它的引用赋给this,继承函数的原型。 
  • 通过this将属性和方法添加至这个对象 
  • 最后返回this指向的新对象,也就是实例

这里遇到过一个小问题,当作为构造函数的时候加了 return 值

function Fn() {
    return 
}
// 这个时候 a 是以Fn 为构造函数的对象
//和
function Fn() {
    return {}
}
var a = new Fn()
// 这个时候  a 是 空对象

如果返回值是一个引用类型,那么this指向的就是那个返回的对象,如果返回值不是一个引用类型那么this还是指向函数的实例

4. call apply bind调用

我们知道call  apply可以改变this指向,来看下call apply bind的实现原理

1.call
我们看个 call 的例子

var count = 1 
var obj = {
    count: 2}
function addNum(num1, num2) {
    return this.count + num1 + num2}

addNum(1,2) // 1 + 1 + 2 = 4
addNum.call(obj, 1,2 ) // 2 + 1 + 2 = 5

      我们可以看出来call 改变了 this指向, 将函数看作是传入对象的树形,然后执行这个对象的属性就行了

Function.prototype.myCall = function(context) {
    context = context || window
    context.fn = this
    var args = [...arguments].slice(1)// 取出除了第一个参数的剩余参数
    var result = context.fn(...args)
    delete context.fn
    return result
}

通过myCall 再运行上面的例子

addNum.myCall(obj, 1,2)

2.apply

apply 和 call 方式类似,只是apply 参数是用数组传递的

Function.prototype.myApply = function(context) {
    context = context || window
    context.fn = this
    var result = context.fn(...arguments[1])
    delete context.fn
    return result
}

3. bind

bind 要比call 和 apply 复杂一些, bind 是返回一个函数,在返回函数调用的时候用apply方法,确定好context 就可以了,bind 还可以支持 new 关键字

Function.prototype.myBind = function(context) {
    context = context || window // 传入的 需要绑定的对象
    var _this = this // 当前调用myBind的函数实例
    var args = [...arguments].slice(1)
    return function F() { // 这个时候返回一个函数
        if( this.instanceof F) { // 通过new 的方式调用
            return new _this(...args, ...arguments)
        }
        return _this.apply(context, args)
    }
}

5. new 的原理及模拟

当new F() 之后会发生这几件事

  1. 一个对象被创建 并继承 F.prototype
  2. 继承F的内部属性
  3. 返回这个创建的对象

下面就来实现一个new ,由于关键字不能覆盖,我们使用函数方式模拟

function F() {
    this.name = '123'
}
F.prototype.showName = function() {
    console.log(this.name)
}

function myNew() {
    // 1.第一步:创建新对象并继承 原型
    var obj = new Object()
    // 获取到需要继承的函数的原型对象
    var F = [].shift.call(arguments)
    obj.__proto__ = F.prototype
    
    // 第二步:继承F的内部属性
    F.apply(obj, arguments)
    // 第三步 返回这个对象
    return obj
}
// 运行上面代码返回一个 对象
myNew(F)