this指针与Call/Apply/Bind,new操作符
this 指针的指向
- 对象属性赋值方法,方法内 this 指向对象本身,
- 如果是变量赋值方法,方法内 this 指向 window
- (上述可以认为是一个全局定义的函数赋值给变量,map、foreach 内部function 也可以认为是全局定义的function,故也是 window).
- 如果将对象属性定义的方法赋值给变量,方法内 this 指向 window.
- 变量找不到会去外层找,但是this不会,this找不到就是window.
Call
call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。
举个栗子
csharp
function foo () {
console.log(this, this.value)
}
let obj = { value: 2 };
foo.call(obj); // obj, 2
执行步骤可以理解为
- 将函数设置为转换的this对象属性(因为对象中运行函数,this指向该对象)
- 执行该对象中的函数
- 删除对象中该属性
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
功能基本完成了,但有些小瑕疵
- this 可以为null, 为null时指向window
- 既然这个属性时没用的, 如果原目标有
_callFn
这个属性,就误删了, 用symbol()
代替 - 函数是可以有返回值的
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操作符实际进行的工作
- 创造一个新对象,新对象的原型指为构造函数的prototype
- 执行构造函数内的操作,获取构造函数的返回值
- 构造函数有返回值则返回返回值,没有则返回构造对象
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