目录
手写call
和apply
手写 call
call()
:在使用一个指定的 this
值和若干个指定的参数值的前提下调用某个函数或方法。
let foo = {
name: "aq"
}
function getName() {
console.log(this.name);
}
getName(); // undefined
getName.call(foo) // 'aq'
call()
实现的功能如下:
- 改变
this
的指向,指到了getName
getName
函数执行了
第一步
上述方式等同于:
let foo = {
name: "aq",
getName: function () {
console.log(this.name)
}
}
foo.getName() // 'aq'
这个时候getName
指向了foo
,但是这样却给foo
本身添加了一个属性,但是我们可以用delete
删除。
所以可以将步骤分为:
- 将该函数设置为对象的属性;
- 执行该函数;
- 删除该函数。
所以可以实现一版:
// 第一版
Function.prototype.call2 = function(context) {
// 首先要获取调用call的函数,用this可以获取
context.fn = this;
context.fn();
delete context.fn;
}
// 测试一下
let foo = {
name: 'aq'
};
function getName() {
console.log(this.name);
}
getName.call2(foo); // 'aq'
第二步
call
除了可以指定 this
还可以指定参数
// 第二版
Function.prototype.call2 = function(context) {
// 首先要获取调用call的函数,用this可以获取
context.fn = this;
let arg = [...arguments].slice(1)
context.fn(...arg);
delete context.fn;
}
// 测试一下
let foo = {
name: 'aq'
};
function getName(age,gender) {
console.log(age);
console.log(gender);
console.log(this.name);
}
getName.call2(foo,19,1); // 19 1 'aq'
第三步
- 当指定的对象为
null
或undefined
的情况下
var name = 'aq'
function getName(){
console.log(this.name);
}
getName.call(null) // 'aq'
getName.call(undefined) // 'aq'
- 被调用函数有返回值的情况下
function getName(){
return {
text: "我有返回值"
}
}
getName.call() // {text: '我有返回值'}
所以最后终写法:
Function.prototype.call2 = function(context,...args) {
if(typeof context === 'undefined' || context === null){
context = window
}
let symbolFun = Symbol();
context[symbolFun] = this;
let result = context[symbolFun](...args);
delete context[symbolFun];
return result
}
手写 apply
apply 的实现跟 call 类似,只是入参不一样,apply为数组
所以最后终写法:
Function.prototype.call2 = function(context,args) {
if(typeof context === 'undefined' || context === null){
context = window
}
let symbolFun = Symbol();
context[symbolFun] = this;
let result = context[symbolFun](...args);
delete context[symbolFun];
return result
}
手写 bind
Function
实例的 bind()
方法创建一个新函数,当调用该新函数时,它会调用原始函数并将其this
关键字设置为给定的值,同时,还可以传入一系列指定的参数,这些参数会插入到调用新函数时传入的参数的前面。
由此可见bind
函数的两个特点:
- 返回一个函数
- 可以传入参数
返回函数模拟实现
var foo = {
value: 1
}
function bar () {
console.log(this.value)
}
var bindFoo = bar.bind(foo);
bindFoo(); // 1
关于this
的指向,我们可以利用call
或apply
实现
// 版本一
Function.prototype.bind2 = function (context) {
let self = this;
// 当返回函数给外部之后 外部执行的 this就会被改变 所以这里要保存this
// 被绑定函数可能会有返回值 这里将执行结果返回出去
return function () {
return self.apply(context)
}
}
传参模拟实现
var foo = {
value: 1
};
function bar(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFoo = bar.bind(foo, 'daisy');
bindFoo('18');
// 1
// daisy
// 18
当需要传 name 和 age 两个参数时,可以在 bind 的时候,只传一个 name,在执行返回的函数的时候,再传另一个参数 age。
这里如果不适用rest
,使用arguments
进行处理:
// 第二版
Function.prototype.bind2 = function (context) {
var self = this;
// 获取bind2函数从第二个参数到最后一个参数
var args = Array.prototype.slice.call(arguments, 1);
return function () {
// 这个时候的arguments是指bind返回的函数传入的参数
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(context, args.concat(bindArgs));
}
}
构造函数效果实现
bind
还有一个特点:
一个绑定函数也能使用new
操作符创建对象:这种行为就像把原函数当成构造器。提供的this
值被忽略,同时调用时的参数被提供给模拟函数。
也就是说当bind
返回的函数作为构造函数的时候,bind
指定的this
值会失效,但是传入的参数依然生效。举个栗子:
var value = 2;
var foo = {
value: 1
};
function bar(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = 'kevin';
var bindFoo = bar.bind(foo, 'daisy');
var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
尽管在全局和 foo
中都声明了 value
值,最后依然返回了 undefined
,说明绑定的 this
失效了
// 第三版
Function.prototype.bind2 = function (context,...args) {
let self = this;
let fBind = function (...args2) {
// 如果调用这个函数是构造函数实例调用的 那么 this 指向 该实例 否则 this 指向 window
return self.apply(this instanceof fBind ? this: context ,[...args,...args2])
}
// 这里再将原型赋值给你新创建的函数
fBind.prototype = this.prototype
return fBind
}
构造函数效果优化实现
我们直接将 fBind.prototype = this.prototype
,假如我直接修改 fBind.prototype
, 这意味着绑定函数的 prototype
也是会被修改的。所以这里我们用一个空的构造函数中转一下
// 第四版
Function.prototype.bind2 = function (context,...args) {
let self = this;
let middle = function () {}
let fBind = function (...args2) {
// 如果调用这个函数是构造函数实例调用的 那么 this 指向 该实例 否则 this 指向 window
return self.apply(this instanceof fBind ? this: context ,[...args,...args2])
}
middle.prototype = this.prototype
fBind.prototype = new middle();
return fBind
}
最终版
调用 bind 的不是函数时,提示错误:
Function.prototype.bind2 = function (context,...args) {
if (typeof this !== "function") {
throw new Error("提示错误信息");
}
let self = this;
let middle = function () {}
let fBind = function (...args2) {
return self.apply(this instanceof fBind ? this: context ,[...args,...args2])
}
middle.prototype = this.prototype
fBind.prototype = new middle();
return fBind
}
手写 new
new
运算符允许开发人员创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。根据
MDN
的描述,当使用new
关键字调用函数时,该函数将被用作构造函数。new
将执行以下操作:
- 创建一个空的简单 JavaScript 对象。为方便起见,我们称之为 newInstance。
- 如果构造函数的 prototype 属性是一个对象,则将 newInstance 的 [[Prototype]] 指向构造函数的 prototype 属性,否则 newInstance 将保持为一个普通对象,其 [[Prototype]] 为
Object.prototype。- 使用给定参数执行构造函数,并将 newInstance 绑定为 this 的上下文(换句话说,在构造函数中的所有 this 引用都指向 newInstance)。
- 如果构造函数返回非原始值,则该返回值成为整个 new 表达式的结果。否则,如果构造函数未返回任何值或返回了一个原始值,则返回 newInstance。(通常构造函数不返回值,但可以选择返回值,以覆盖正常的对象创建过程。)
function objectFactory() {
// 1.首先创建一个空的JavaScript对象
let newInstance = Object.create();
// 将构造函数提取出来 arguments 里面剩余的就是参数
let Constructor = [].shift.call(arguments);
// 2.将 newInstance 的 [[prototype]] (__proto__) 属性指向 构造函数的prototype
Object.setPrototypeOf(newInstance, Constructor.prototype);
// 3.使用指定参数执行构造函数 将newInstance绑定为this
let result = Constructor.apply(newInstance,arguments)
// 4.判断构造函数是否返回了对象 否则返回newInstance
return typeof result === 'object' ? result : newInstance
}
测试一下
function objectFactory() {
// 1.首先创建一个空的JavaScript对象
let newInstance = Object.create(null);
// 将构造函数提取出来 arguments 里面剩余的就是参数
let Constructor = [].shift.call(arguments);
// 2.将 newInstance 的 [[prototype]] (__proto__) 属性指向 构造函数的prototype
Object.setPrototypeOf(newInstance, Constructor.prototype);
// 3.使用指定参数执行构造函数 将newInstance绑定为this
let result = Constructor.apply(newInstance,arguments)
// 4.判断构造函数是否返回了对象 否则返回newInstance
return typeof result === 'object' ? result : newInstance
}
function foo (name,age) {
this.name = name;
this.age = age
// return {}
}
foo.prototype.getInfo = function () {
console.log(this.name + '今年' + this.age + '岁');
}
let obj = objectFactory(foo,'敏敏',18);
obj.getInfo(); // 敏敏今年18岁
console.log(obj); // foo { name: '敏敏', age: 18 }