彻底搞懂 js 的 this 指向

this 的绑定规则,决定了 this 指向的是什么

  • 默认绑定
  • 隐式绑定
  • 显示绑定
  • new 绑定
2.2 默认绑定

独立函数调用, 使用的是 默认绑定

独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用

// 默认绑定 : 独立函数调用

//  案例1
function foo(params) {
  console.log(this);
}

foo()

//  案例2
function foo1(params) {
  console.log(this);
}
function foo2(params) {
  console.log(this);
  foo1()
}
function foo3(params) {
  console.log(this);
  foo2()
}
foo3()

// 案例 3
var obj = {
  name: "ddg",
  foo: function () {
    console.log(this);
  }
}

var bar = obj.foo
bar() // 此时的 bar 调用的时候 没有主题,所以是 window

console.log('案例四~~~~~~~~~~~~~~~');
// 案例4 
function foo4(params) {
  console.log(this);
}
var anli4 = {
  name: "呆呆狗",
  foo: foo4
}
var bar4 = anli4.foo
bar4() // bar4 在调用的时候 还是 独立函数调用 所以还是 window

// 案例5
console.log('案例 ~~~~~~~~');
function foo5() {
  return function () {
    console.log(this);
  }
}

var fn5 = foo5()
fn5() //  fn5 在调用的时候 还是 独立调用 所以还是 window
2.3 隐式绑定

隐式绑定比较常见的就是 通过 某个对象进行调用的

就是它的调用位置中,是通过某个对象发起的函数调用

  • 隐式绑定必须在调用的对象内部有一个对函数的引用(比如一个属性)
  • 如果没有这样的引用,在进行调用时,会报找不到该函数的错误
  • 正是通过这个引用,间接的将 this 绑定到这个对象上
// 案例 1
// function foo() {
//   console.log(this);
// }
// var obj = {
//   name: "ddg",
//   foo: foo
// }
// obj.foo()

// 案例 2
// var obj = {
//   name: "ddg",
//   eating: function () {
//     console.log(this.name + '  在吃饭');
//   },
//   running: function () {
//     console.log(this.name + '  在跑步');
//   },
// }
// obj.eating()
// obj.running()

// 案例3
var obj1 = {
  name: "obj1",
  foo: function () {
    console.log(this);
  }
}
var obj2 = {
  name: "obj2",
  bar: obj1.foo
}
obj2.bar() // 此时的 this 指向的是 obj2, 调用主题是 obj2

var fn = obj2.bar
fn() // window
2.4 显示绑定 及 call - apply -bind 实现

call 和 apply 在执行函数的时候,是可以明确绑定函数this ,这种被称为显示绑定

2.4.1 call 实现

函数的实现 会有很多的 edge case (边界判断) , 就是 特殊的情况

// 给所有函数添加 _call 方法
Function.prototype._call = function (thisArg, ...restParameters) {
  // 执行 调用的那个函数 , 其实是一个隐式绑定
  // 如果是一个 单纯的 数字, Object(数字)  可以转换成 对象模式
  // Object(null/undefined) 会返回一个空对象

  // 1. 获取需要被执行的函数
  var fn = this

  // 2.对 thisArg 转换成对象类型 ( 防止它传入的是非对象类型 )
  thisArg = thisArg ? Object(thisArg) : window

  // 3.调用需要被执行的函数
  thisArg.fn = fn
  var result = thisArg.fn(...restParameters)
  delete thisArg.fn
  // 4.将 结果返回
  return result
}

function foo(num1, num2) {
  console.log('foo 被执行', this, num1, num2);
  return num1 + num2
}

var result1 = foo._call({ name: "呆呆狗" }, 100, 200)
var result2 = foo._call('呆呆狗')
var result13 = foo._call(null)
console.log('返回结果:', result1);
2.4.2 apply 实现
Function.prototype._apply = function (thisArg, argArray) {
  // 当传递一个参数的时候, 会报错,argArray 是 没有值的,此时的 argArray 就是 undefined
  // 解决方案:1. 可以判断 argArray 有没有值

  // 1. 拿到 函数
  var fn = this
  // 2. 转换成 对象  (如果 thisArg 是 0 或者 '' 呢? Boolean(0或者空字符串) 的结果是 false)
  thisArg = thisArg ? Object(thisArg) : window
  // 3. 执行
  thisArg.fn = fn
  // var result = thisArg.fn(...argArray)
  // 解决 argArray 不传递的情况 , 方案 1
  // var result
  // if (!argArray) {
  //   // argArray 没有传递参数的时候
  //   result = thisArg.fn()
  // } else {
  //   result = thisArg.fn(...argArray)
  // }

  // 解决 argArray 不传递的情况 , 方案 2
  argArray = argArray ? argArray : []
  var result = thisArg.fn(...argArray)

  delete thisArg.fn
  return result
}

function sum(num1, num2) {
  console.log('sum 被调用');
  return num1 + num2
}
// 如果是一个参数呢
function sum1(params) {
  return params
}

// 如果没有传递参数 ,
function sum2() {

}

var result = sum._apply({ name: "呆呆狗" }, [10, 20])
var result1 = sum1._apply({ name: "呆呆狗" }, [10])
var result2 = sum2._apply({ name: "呆呆狗" },)

console.log('结果1', result);
console.log('结果2', result1);
console.log('结果3', result2);
2.4.3 bind 实现
Function.prototype._bind = function (thisArg, ...argArray) {
  // 1. 获取到真实调用的函数
  var fn = this
  // 2.绑定this
  thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg) : window
  function proxyFn(...args) {
    // 3. 
    var finalArgs = [...argArray, ...args]
    thisArg.fn = fn
    var result = thisArg.fn(...finalArgs)
    delete thisArg.fn
    return result
  }
  return proxyFn
}

function sum(num1, num2, num3, num4) {
  console.log('sum被执行', this);
  console.log('参数', num1, num2, num3, num4);
  return 20
}

var bar = sum._bind({ name: "呆呆狗" }, 10, 20)
var result = bar(30, 40)
console.log(result);
2.5 new 绑定

new 函数关键词来调用函数,会执行如下的操作

  1. 创建一个全新的对象
  2. 这个新对象会被执行 prototype 链接
  3. 这个新对象 会绑定到函数 调用的this 上 (this 的绑定在这个步骤完成)
  4. 如果函数没有返回其他对象,表达式会返回这个新对象
2.6 其他情况的 this
  1. 定时器的this 是 window
  2. 点击事件的 this 是 点击的那个DOM本身
  3. 数组的 forEach、map、filter、find 等函数,的第二个参数 是this 的指向
2.7 绑定规则的优先级
  • 默认绑定 的优先级是最低的
  • 显示绑定 优先级高于 隐式绑定 ,不论是 apply 、 call 还是bind
  • new 绑定 优先级 高于 隐式绑定
  • new 绑定 优先级高于 显示绑定

new 绑定 > 显示绑定(apply/call/bind) > 隐式绑定 (obj.foo() ) > 默认绑定 ( 独立函数调用 )

// var obj = {
//   name: "ddg",
//   foo: function () {
//     console.log(this);
//   }
// }

// obj.foo()
// obj.foo.call(window) // 此时的 this 是 window
// obj.foo.call("abc") // 此时的 this 是 “abc”
// 所以 call apply 显示绑定 高于 隐式绑定

// 2 bind 隐式绑定
// var bar = obj.foo.bind("bind")
// bar()

// 3. 更明显的比较
function foo() {
  console.log(this);
}
var obj = {
  name: "obj",
  foo: foo.bind("aaa")
}
obj.foo()
// bind 的显示绑定 也高于 隐式绑定
var obj = {
  name: "obj",
  foo: function () {
    console.log(this);
  }
}

// new 的优先级 高于 隐式绑定
var f = new obj.foo()  // 此时的this 是 foo
// 结论 new 关键字 不能 和 apply/call 一起来使用

// new bind
function foo() {
  console.log(this);
}
var bar = foo.bind("aaa")
var obj = new bar() // foo 
2.8 忽略 显示绑定
function foo() {
  console.log(this);
}

foo.apply("abc")
foo.call({})

// 当我们传入 null 或者  undefined ,函数内部会把this 绑定为 window
foo.call(null) // window
foo.call(undefined)// window

var bar = foo.bind(null)
bar() // window
2.9 间接函数引用
var obj1 = {
  name: "obj1",
  foo: function () {
    console.log(this);
  }
}
var obj2 = {
  name: "obj2"
};
// 如果 第九行,不加分号,那么 执行会报错
// 如果不加, 下面用小括号 括起来的 代码 会看成 和 上面的 obj2 大括号是一个整体

// obj2.bar = obj1.foo()
// obj2.bar() // obj2

(obj2.bar = obj1.foo)() // window , 这里会作为一个独立函数调用
2.10 箭头函数

箭头函数 不会绑定 this、argument 属性

箭头函数不能作为构造函数来使用 ( 不能和 new 一起来使用,会抛出错误 )

箭头函数的 this 是根据外层作用域来决定 this

2.11 面试题 1
var name = "window";

var person = {
  name: "person",
  sayName: function () {
    console.log(this.name);
  }
};

function sayName() {
  var sss = person.sayName;
  sss(); // window: 独立函数调用
  person.sayName(); // person: 隐式调用
  (person.sayName)(); // person: 隐式调用 。 这里的小括号 加不加没有影响
  (b = person.sayName)(); // window: 赋值表达式(独立函数调用) 。 因为这里有一个赋值,然后再调用
}

sayName();
2.12 面试题2
var name = 'window'

var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person2 = { name: 'person2' }

// 定义 对象 不产生作用域

// person1.foo1(); // person1(隐式绑定)
// person1.foo1.call(person2); // person2(显示绑定优先级大于隐式绑定)

// person1.foo2(); // window(不绑定作用域,上层作用域是全局)
// person1.foo2.call(person2); // window , 箭头函数不绑定 this

// person1.foo3()(); // window(独立函数调用)
// foo3调用的时候,绑定的是 person1 , 而 foo3 返回的函数,调用的时候没有 主题,是一个独立函数调用

// person1.foo3.call(person2)(); // window(独立函数调用)
// person1.foo3().call(person2); // person2(最终调用返回函数式, 使用的是显示绑定)

// person1.foo4()(); // person1(箭头函数不绑定this, 上层作用域this是person1)
// person1.foo4.call(person2)(); // person2(上层作用域被显示的绑定了一个person2)
// person1.foo4().call(person2); // person1(上层找到person1)
2.13 面试题 3
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() // person1
person1.foo1.call(person2) // person2(显示高于隐式绑定)

person1.foo2() // person1 (上层作用域中的this是person1)
person1.foo2.call(person2) // person1 (上层作用域中的this是person1)

person1.foo3()() // window(独立函数调用)
person1.foo3.call(person2)() // window
person1.foo3().call(person2) // person2

person1.foo4()() // person1
person1.foo4.call(person2)() // person2
person1.foo4().call(person2) // person1


var obj = {
  name: "obj",
  foo: function() {

  }
}
2.14 面试题 4
var name = 'window'

function Person(name) {
  this.name = name
  this.obj = {
    name: 'obj',
    foo1: function () {
      return function () {
        console.log(this.name)
      }
    },
    foo2: function () {
      return () => {
        console.log(this.name)
      }
    }
  }
}

var person1 = new Person('person1')
var person2 = new Person('person2')

person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2

person1.obj.foo2()() // obj
// foo2 是被 obj 调用的,所以 foo2的this 是 obj

person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj

// 上层作用域的理解
// var obj = {
//   name: "obj",
//   foo: function() {
//     // 上层作用域是全局
//   }
// }

// function Student() {
//   this.foo = function() {

//   }
// }

你可能感兴趣的:(js,js)