参考链接:

在手写之前,最好先了解为什么会出现这个需要改变this指向的场景,以及 bind, call, apply 的作用和它们的区别。

this 是什么

它是函数内部的属性,是函数执行的环境对象,也就是是函数的 this 会指向函数的执行环境。

this 指向哪里

函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。

大致指向总结可以分为以下几种情况(非严格模式):

  1. 函数作为全局环境调用时 ```javascript // 非严格模式 var 声明才会挂在window上,let, const声明不会 var name = “global”; // 相当于 window.name = “global”

function a() { return this.name }

// 此处是在window全局环境中调用a函数,所以内部的this指向window a() === “global”;

  1. 2. 函数作为对象方法调用时
  2. ```javascript
  3. var obj = {
  4. name: "obj",
  5. sayName: function(){
  6. return this.name
  7. }
  8. }
  9. // 此处调用者是obj,this指向obj内部
  10. obj.sayName() === "obj" // true
  11. // 稍微改动一下,添加下面代码:
  12. var sayName = obj.sayName;
  13. // 此时调用者是window,所以false
  14. sayName() === "obj" // false
  1. 函数作为dom节点事件调用时 ```javascript var container3 = document.getElementById(‘container3’)

container3.onclick = function(){ // 指向节点本身 console.log(this) //

container3
}

  1. 4. 作为构造函数实力化方法时
  2. ```javascript
  3. function A(name){
  4. this.name = name;
  5. this.sayName = function(){
  6. // 指向实例对象
  7. console.log(this.name)
  8. }
  9. }
  10. var a = new A('aa');
  11. a.sayName(); // aa
  1. 箭头函数里的this ```javascript var name = ‘window’ var obj = { name:’obj’, fn: function(){
    (function (){ console.log(this.name) })() }
    } // 普通函数,由于闭包函数是window执行的,所以this指向window obj.fn() // window

// 箭头函数的this指向函数创建时的作用域 var obj2 = { name:’obj’, fn: function(){
(()=>{ //改成箭头函数 console.log(this.name) })() }
} obj2.fn()

  1. <a name="WRyvA"></a>
  2. ## this指向怎么改
  3. 众所周知,引用类型在内存中储存的是对象的内存地址,那么作为函数,它有可能在不同的环境(上下文)被执行。当在函数体内部,引用当前环境的其他变量时,函数需要获取当前的运行环境(context),所以,this 出现了,它设计的目的就是在函数体内部,代指函数当前的运行环境。
  4. 那为什么需要改变 this 指向?也好理解,比如上面例子2的场景,如果调用对象被赋值后调用没有绑定 this,那么它原本的指向将会变成window(或者其他调用环境),这时候就需要绑定this的指向了,用到call,bind,apply。
  5. 以上面的例子2为例:
  6. ```javascript
  7. var obj = {
  8. name: "obj",
  9. sayName: function(){
  10. return this.name
  11. }
  12. }
  13. // bind
  14. var sayNameBind = obj.sayName;
  15. var sayNameCall = obj.sayName;
  16. var sayNameApply = obj.sayName;
  17. console.log(sayNameBind.bind(obj)() === "obj") // true
  18. console.log(sayNameCall.call(obj) === "obj") // true
  19. console.log(sayNameApply.apply(obj) === "obj") // true

bind, call, apply 语法和区别

mdn:

apply:fn.apply(thisObj, 数组参数)
定义:应用某一个对象的一个方法,用另一个对象替换当前对象
说明:如果参数不是数组类型的,则会报一个TypeError错误。

call:fn.call(thisObj, arg1, arg2, argN)
apply与call的唯一区别就是接收参数的格式不同。

bind:fn.bind(thisObj, arg1, arg2, argN)
bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。

区别
call、bind 和 apply 的第一个参数都是要改变上下文的对象
call、bind 从第二个参数开始以参数列表的形式展现,apply 第二个参数是数组参数;
call、apply改变了函数的this上下文后便立刻执行该函数,bind 则是返回改变了上下文后的一个函数;

手写

apply

  1. Function.prototype.myApply= function(context, args = []){
  2. // 1. 限制参数类型为数组
  3. if(!Array.isArray(args)) throw new Error('apply的第二个参数必须是数组')
  4. if (!context || context === null) context = window;
  5. // 创造唯一的key值 作为我们构造的context内部方法名
  6. const fn = Symbol();
  7. // 2.将函数挂载到传入的对象
  8. context[fn] = this;
  9. // 3.执行对象的方法
  10. return context[fn](...args);
  11. }
  12. // Test:
  13. var obj = {
  14. name: "obj",
  15. sayName: function(params){
  16. return this.name + params
  17. }
  18. }
  19. var sayNameApply = obj.sayName;
  20. console.log(sayNameApply.myApply(obj, ["test"]) === "objtest") // true

call,与apply的唯一区别就是参数格式不同

  1. Function.prototype.myCall= function(context, ...args){
  2. if (!context || context === null) context = window;
  3. // 创造唯一的key值 作为我们构造的context内部方法名
  4. const fn = Symbol();
  5. // 2.将函数挂载到传入的对象
  6. context[fn] = this;
  7. // 3.执行对象的方法
  8. return context[fn](args);
  9. }
  10. // Test:
  11. var obj = {
  12. name: "obj",
  13. sayName: function(params){
  14. return this.name + params
  15. }
  16. }
  17. var sayNameCall = obj.sayName;
  18. console.log(sayNameCall.myCall(obj, "test") === "objtest") // true

bind

  1. Function.prototype.myBind = function (context, ...args) {
  2. if (!context || context === null) {
  3. context = window;
  4. }
  5. const fn = Symbol();
  6. context[fn] = this;
  7. let _this = this;
  8. const result = function (...innerArgs) {
  9. if (this instanceof _this === true) {
  10. // 此时this指向指向result的实例 这时候不需要改变this指向
  11. this[fn] = _this;
  12. return this[fn](...[...args, ...innerArgs]);
  13. } else {
  14. // 如果只是作为普通函数调用 那就很简单了 直接改变this指向为传入的context
  15. return context[fn](...[...args, ...innerArgs]);
  16. }
  17. };
  18. // 如果绑定的是构造函数 那么需要继承构造函数原型属性和方法
  19. // 实现继承的方式: 使用Object.create
  20. result.prototype = Object.create(this.prototype);
  21. return result;
  22. };
  23. // Test:
  24. var obj = {
  25. name: "obj",
  26. sayName: function(params){
  27. return this.name + params
  28. }
  29. }
  30. var sayNameBind = obj.sayName;
  31. console.log(sayNameBind.myBind(obj, "test")()) // true