this整理
2024-04-04
共 1137 字
预计阅读 6 分钟

this📑

本质上是个对象,谁调用就指谁,所以使用 call/bind/apply 时通过 this 获取调用的对象

  • 全局对象中的 this 指向全局,严格模式下是 undefined
  • 函数的 this 在函数调用时已经确定,fun(),其中 fun 是调用者,如果 fun 被其他对象拥有,比如 window.fun(),那么 this 指向拥有者 window(最后一个调用的),否则按第一种情况判断
  • 对象的 this 调用由于{}不会形成单独的作用域所以指的是全局对象(注意:区分作用域【定义时确立】和 this 指向【执行时且可以被改变】)
  • apply/bind/call 等用于灵活改变 this 指向,第一个参数为 this 的真正指向对象
  • new 构造函数 this 指向实例出来的对象
  • 箭头函数 this 指向同外层作用域,且指向函数定义时的 this 而非执行时(关联知识点:箭头函数跟普通函数的区别)

有两种情况容易发生隐式丢失问题

  • 使用另一个变量来给函数取别名
  • 将函数作为参数传递时会被隐式赋值,回调函数丢失 this 绑定,普通传参及 setTimeout 均为此种情况

情况一:变量别名导致隐式丢失

function foo() {
  console.log(a); // 这里的a表示的是作用域下的a
  console.log(this.a); // 这里的a是this对象中的a属性
}
// 注意这里的foo函数不能使用箭头函数,不然直接指向定义时外层的this即window
var obj = { a: 1, foo };
var a = 2;
obj.foo(); // obj调用的foo,this指向obj,打印1
var foo2 = obj.foo;
foo2(); // 隐式丢失,this指向的是window

img

关于为什么

JavaScript 中的this关键字用于指向当前正在执行的函数的上下文对象。它的存在是为了提供对当前上下文的访问权限和操作。

【建议 👍】再来 40 道 this 面试题酸爽继续(1.2w 字用手整理)

关于 call/bind/apply📑

  • 使用.call()或者.apply()的函数是会直接执行的,即 obj1.fun.call(obj2)等价于 obj1.fun(),this 为 obj2
  • bind()是创建一个新的函数,需要手动调用才会执行,返回一个 bound 函数
  • .call().apply()用法基本类似,不过call接收若干个参数,而apply接收的是一个数组
  • 如果call、apply、bind接收到的第一个参数是空或者null、undefined的话,则会忽略这个参数。
  • forEach、map、filter函数的第二个参数也是能显式绑定this

call

call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

非常好理解

  1. 将函数设为对象的属性
  2. 执行该函数
  3. 删除该函数
Function.prototype.myCall = function (context, ...args) {
  // 获取调用call的函数
  var fn = this;
  var result;

  // 如果没有传递上下文,则默认为全局对象(非严格模式下)
  context = context || window;

  // 为上下文对象创建一个唯一的属性,用于保存要调用的函数
  var uniqueProp = Symbol("call");
  context[uniqueProp] = fn;

  // 执行函数
  result = context[uniqueProp](...args);

  // 删除临时创建的函数属性
  delete context[uniqueProp];

  return result;
};

apply

Function.prototype.myApply = function (context, argsArray) {
  // 保存原始函数
  var fn = this;
  var result;

  // 如果没有传递上下文对象,则默认为全局对象(非严格模式下)
  context = context || window;

  // 为上下文对象创建一个唯一的属性,用于保存要调用的函数
  var uniqueProp = Symbol("apply");
  context[uniqueProp] = fn;

  // 执行函数
  if (argsArray) {
    // 使用展开操作符 `...` 将参数数组传递给函数
    result = context[uniqueProp](...argsArray);
  } else {
    result = context[uniqueProp]();
  }

  // 删除临时创建的函数属性
  delete context[uniqueProp];

  return result;
};

bind

// 第一版 修改this指向,合并参数
Function.prototype.bindFn = function bind(thisArg) {
  if (typeof this !== "function") {
    throw new TypeError(this + "must be a function");
  }
  // 存储函数本身
  var self = this;
  // 去除thisArg的其他参数 转成数组
  var args = [].slice.call(arguments, 1);
  var bound = function () {
    // bind返回的函数 的参数转成数组
    var boundArgs = [].slice.call(arguments);
    // apply修改this指向,把两个函数的参数合并传给self函数,并执行self函数,返回执行结果
    // 内外arguments不同
    return self.apply(thisArg, args.concat(boundArgs));
  };
  return bound;
};
// 测试
var obj = {
  name: "若川",
};
function original(a, b) {
  console.log(this.name);
  console.log([a, b]);
}
var bound = original.bindFn(obj, 1);
bound(2); // '若川', [1, 2]

ES3

var args = ["1", "dierge"];
var fnStr = "context.fn(";
for (var i = 0; i < args.length; i++) {
  fnStr += i == args.length - 1 ? args[i] : args[i] + ",";
}
fnStr += ")";
console.log(fnStr); // context.fn(1,dierge);
感谢您的阅读,如果对文章内容有任何疑问或者建议,欢迎在掘金社区私信我