this指针与Call/Apply/Bind,new操作符

this指针与Call/Apply/Bind,new操作符

this 指针的指向

  1. 对象属性赋值方法,方法内 this 指向对象本身,
  2. 如果是变量赋值方法,方法内 this 指向 window
  3. (上述可以认为是一个全局定义的函数赋值给变量,map、foreach 内部function 也可以认为是全局定义的function,故也是 window).
  4. 如果将对象属性定义的方法赋值给变量,方法内 this 指向 window.
  5. 变量找不到会去外层找,但是this不会,this找不到就是window.

Call

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

举个栗子

csharp 复制代码
function foo () { 
	console.log(this, this.value) 
}
let obj = { value: 2 };
foo.call(obj); // obj, 2

执行步骤可以理解为

  1. 将函数设置为转换的this对象属性(因为对象中运行函数,this指向该对象)
  2. 执行该对象中的函数
  3. 删除对象中该属性
js 复制代码
Function.prototype.myCall = function(ctx, ...args) {
	ctx._callFn = this;
	ctx._callFn(...args);
	delete ctx._callFn;
}
function foo (...args) { 
	console.log(this, this.value, ...args) 
}
let obj = { value: 2 };
foo.myCall(obj, 1, 2, 3); // 2, 1, 2, 3

功能基本完成了,但有些小瑕疵

  1. this 可以为null, 为null时指向window
  2. 既然这个属性时没用的, 如果原目标有_callFn这个属性,就误删了, 用symbol()代替
  3. 函数是可以有返回值的
js 复制代码
Function.prototype.myCall = function (ctx , ...args) {
  const fnCtx = ctx || window;
  const fn = Symbol();
  fnCtx[fn] = this;
  const res = fnCtx.fn(...args);
  delete fnCtx[fn];
  return res;
};
function foo (...args) { 
  return this.value;
}
window.value = 3
let obj = { value: 2 };
console.log(foo.myCall(obj)); // 2
console.log(foo.myCall()); // 3

Apply

apply和Call灰常相似,只是把多个参数,改成参数数组, 只要把...args改成arg即可

js 复制代码
Function.prototype.myApply = function (ctx , argArr) {
  const fnCtx = ctx || window;
  const fn = Symbol();
  fnCtx[fn] = this;
  const res = fnCtx[fn](...argArr);
  delete fnCtx[fn];
  return res;
};
function foo (...args) { 
  console.log(...args);
  return this.value;
}
let obj = { value: 2 };
console.log(foo.myApply(obj, [1, 2])); // 1, 2     2

Bind

MDN 中这样介绍bind:

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。

bind 其实跟call 很像,唯一区别就是bind返回的是函数, 而call返回的是调用函数后的值

举个栗子

js 复制代码
let obj = { value: 2 };
function foo(...args) { console.log(this.value, ...args) }
foo.bind(obj, 666)(); // 2 666
foo.call(obj, 666); // 2 666

因此,我们可以再call的基础上实现bind

js 复制代码
let obj = { value: 2 };
function foo(...args) { console.log(this.value, ...args) }
Function.prototype.myBind = function(ctx, ...args){
  const preArgs = args;
  return (...args) => this.call(ctx, ...preArgs, ...args); // 箭头函数的this指向外层
}
const bindFn = foo.myBind(obj, 6, 7)
bindFn(8, 9);  // 2, 6, 7, 8 , 9

new操作符

new 运算符允许开发人员创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例

辣么问题来了,如何手写一个new操作符

答案是不行的,因为new操作符是保留字,所以只能用function来模拟一个

new操作符实际进行的工作

  1. 创造一个新对象,新对象的原型指为构造函数的prototype
  2. 执行构造函数内的操作,获取构造函数的返回值
  3. 构造函数有返回值则返回返回值,没有则返回构造对象
js 复制代码
// 自定义new函数
function myNew(fn, ...args) {
  let obj = Object.create(fn.prototype);
  const result = fn.apply(obj, args);
  return result && typeof result === 'object' ? result : obj;  
}

// 测试代码
function Otaku (name, age) {
    this.name = name;
    this.age = age;
    this.habit = 'Games';
}
Otaku.prototype.strength = 60;
Otaku.prototype.sayYourName = function () {
    console.log('I am ' + this.name);
};

var person = myNew(Otaku, 'Kevin', '18');
console.log(person.name); // Kevin
console.log(person.habit); // Games
console.log(person.strength); // 60
person.sayYourName(); // I am Kevin

参考

  1. MDN
  2. JavaScript深入之call和apply的模拟实现
相关推荐
小曲曲44 分钟前
接口上传视频和oss直传视频到阿里云组件
javascript·阿里云·音视频
学不会•2 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
EasyNTS3 小时前
H.264/H.265播放器EasyPlayer.js视频流媒体播放器关于websocket1006的异常断连
javascript·h.265·h.264
活宝小娜4 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点4 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow4 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o4 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
刚刚好ā5 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
yqcoder7 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
会发光的猪。7 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js