共计 8306 个字符,预计需要花费 21 分钟才能阅读完成。
一 目录
不折腾的前端,和咸鱼有什么区别
目录 |
---|
一 目录 |
二 前言 |
三 最终实现 |
3.1 手写 call |
3.2 手写 apply |
3.3 手写 bind |
四 Arguments 对象 |
五 call |
5.1 原生 call |
5.2 手写 call |
六 apply |
6.1 原生 apply |
6.2 手写 apply |
七 bind |
7.1 原生 bind |
7.2 手写 bind |
八 题目 |
8.1 this 指向问题 1 |
8.2 this 指向问题 2 |
九 参考文献 |
二 前言
返回目录
面试官:手写一个 call/apply/bind
。
工欲善其事,必先利其器,咱们先理解一下这 3 者有什么区别:
call
的应用
function Product (name, price) {
this.name = name;
this.price = price;
}
function Food (name, price) {Product.call(this, name, price);
this.category = 'food';
}
const food = new Food('cheese', 5);
console.log(food.name); // 'cheese'
call
:能够扭转函数指向,第一个参数是要扭转指向的对象,之后的参数模式是arg1, arg2...
的模式apply
:根本同call
,不同点在于第二个参数是一个数组[arg1, arg2...]
bind
:扭转this
作用域会返回一个新的函数,这个函数不会马上执行
三 最终实现
返回目录
上面咱们列一下明天的实现目标:
- 手写
call
- 手写
apply
- 手写
bind
3.1 手写 call
返回目录
Function.prototype.myCall = function(context = globalThis) {
// 设置 fn 为调用 myCall 的办法
context.fn = this;
// 获取残余参数
const otherArg = Array.from(arguments).slice(1);
// 调用这个办法,将残余参数传递进去
context.fn(otherArg);
// 将这个办法的执行后果传给 result
let result = context.fn();
// 删除这个变量
delete context.fn;
// 返回 result 后果
return result;
};
this.a = 1;
const fn = function() {
this.a = 2;
console.log(this.a);
}
fn.myCall(fn);
3.2 手写 apply
返回目录
Function.prototype.myApply = function(context = globalThis, arr) {
// 设置 fn 为调用 myCall 的办法
context.fn = this;
let result;
// 如果存在参数,则传递进去
// 将后果返回给 result
if (arr) {result = context.fn(arr);
} else { // 否则不传
result = context.fn();}
// 删除这个变量
delete context.fn;
// 返回 result 后果
return result;
};
this.a = 1;
const fn = function() {
this.a = 2;
console.log(this.a);
}
fn.myApply(fn);
3.3 手写 bind
返回目录
Function.prototype.myBind = function(context = globalThis) {
// 设置 fn 为调用 myCall 的办法
const fn = this;
// 获取该办法残余参数
const otherArg = [...arguments].slice(1);
// 设置返回的一个新办法
const result = function() {
// 获取返回办法体的参数
const resultArg = [...arguments];
// 如果是通过 new 调用的,绑定 this 为实例对象
if (this instanceof result) {fn.apply(this, otherArg.concat(resultArg));
} else { // 否则一般函数模式绑定 context
fn.apply(context, otherArg.concat(resultArg));
}
}
// 绑定原型链
result.prototype = Object.create(fn.prototype);
// 返回后果
return result;
};
this.a = 1;
const fn = function() {
this.a = 2;
console.log(this.a);
}
fn.myBind(fn);
fn();
OK,懂了么,咱们发车持续深造~
四 Arguments 对象
返回目录
- MDN – Arguments
arguments
是一个对应于传递给函数的参数的类数组对象。
function fun(a, b, c) {console.log(arguments[0]); // 1
console.log(arguments[1]); // 2
console.log(arguments[2]); // 3
}
arguments
对象不是一个 Array
。
它相似于 Array
,但除了 length
属性和索引元素之外没有任何 Array
属性。
将 arguments
转为数组:
// ES5
var arg1 = Array.prototype.slice.call(arguments);
var arg2 = [].slice.call(arguments);
// ES6
var arg3 = Array.from(arguments);
var arg4 = [...arguments];
在手写 call/bind/apply
过程中,会用到 arguments
来获取办法体的传参,就好比手写 call
过程中,通常咱们通过 Array.from(arguments).slice(1)
来获取第二个及前面的参数。
五 call
返回目录
5.1 原生 call
返回目录
- MDN – call
call()
办法应用一个指定的 this
值和独自给出的一个或多个参数来调用一个函数。
留神:该办法的语法和作用与
apply()
办法相似,只有一个区别,就是call()
办法承受的是一个参数列表,而apply()
办法承受的是一个蕴含多个参数的数组。
-
语法:
function.call(thisArg, arg1, arg2, ...)
thisArg
:可选的。在function
函数运行时应用的this
值。arg1, arg2, ...
:指定的参数列表
function Product (name, price) {
this.name = name;
this.price = price;
}
function Food (name, price) {Product.call(this, name, price);
this.category = 'food';
}
const food = new Food('cheese', 5);
console.log(food.name); // 'cheese'
5.2 手写 call
返回目录
首先咱们得搞明确 call
的个性:
- 如果
obj.call(null)
,那么this
应该指向window
- 如果
obj1.call(obj2)
,那么谁调用它,this
指向谁(这里就是obj2
了) call
能够传入多个参数,所以能够利用arguments
这个字段来获取所有参数。将arguments
转换数组后,获取除第一个参数外的其余参数- 设置一个变量,用完能够删掉它
综上:
手写 call 的 JS 代码:
Function.prototype.myCall = function(context = globalThis) {
// 设置 fn 为调用 myCall 的办法
context.fn = this;
// 获取残余参数
const otherArg = Array.from(arguments).slice(1);
// 调用这个办法,将残余参数传递进去
context.fn(otherArg);
// 将这个办法的执行后果传给 result
let result = context.fn();
// 删除这个变量
delete context.fn;
// 返回 result 后果
return result;
};
this.a = 1;
const fn = function() {
this.a = 2;
console.log(this.a);
}
fn.myCall(fn);
小伙伴稍稍了解下,搞清楚它外部的流程,而后咱们实际一下:
防抖函数绑定手写 call
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title> 手写 call</title>
</head>
<body>
<button class="btn">123</button>
<script>
(function() {Function.prototype.myCall = function(context) {
const newContext = context || window;
newContext.fn = this;
const otherArg = Array.from(arguments).slice(1);
newContext.fn(otherArg);
const result = newContext.fn(otherArg);
delete newContext;
return result;
};
const debounce = function(fn) {
let timer = null;
return function() {clearTimeout(timer);
timer = setTimeout(() => {fn.myCall(this, arguments);
}, 1000);
}
}
let time = 0;
const getNumber = function() {console.log(++time);
}
const btn = document.querySelector('.btn');
btn.addEventListener('click', debounce(getNumber));
})()
</script>
</body>
</html>
这样咱们就摸清了手写 call
。
六 apply
返回目录
6.1 原生 apply
返回目录
- MDN – apply
apply()
办法调用一个具备给定 this
值的函数,以及以一个数组(或类数组对象)的模式提供的参数。
-
语法:
function.apply(thisArg, [argsArray])
thisArg
:必选的。在function
函数运行时应用的this
值[argsArray]
:可选的。一个数组或者类数组对象,其中的数组元素将作为独自的参数传给func
函数。
const numbers = [5, 6, 2, 3, 7];
const max = Math.max.apply(null, numbers);
console.log(max); // 7
const min = Math.min.apply(null, numbers);
console.log(min); // 2
6.2 手写 apply
返回目录
上面咱们开始尝试手写 apply
,记住这个办法和 call
相似,了解起来也不难:
Function.prototype.myApply = function(context = globalThis, arr) {
// 设置 fn 为调用 myCall 的办法
context.fn = this;
let result;
// 如果存在参数,则传递进去
// 将后果返回给 result
if (arr) {result = context.fn(arr);
} else { // 否则不传
result = context.fn();}
// 删除这个变量
delete fcontext.fnn;
// 返回 result 后果
return result;
};
this.a = 1;
const fn = function() {
this.a = 2;
console.log(this.a);
}
fn.myApply(fn);
用自定义 apply
+ 防抖实际一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>DOM 操作 </title>
</head>
<body>
<button class="btn">123</button>
<script>
(function() {Function.prototype.myApply = function(context, arr) {
const newContext = context || window;
newContext.fn = this;
console.log(newContext.fn)
if (!arr) {result = newContext.fn();
} else {result = newContext.fn(arr);
}
delete newContext;
return result;
};
const debounce = function(fn, number) {
let timer = null;
return function() {clearTimeout(timer);
timer = setTimeout(() => {fn.myApply(this, number);
}, 1000);
}
}
const getNumber = function(time) {console.log(time);
}
let number = [1, 2, 3, 4, 5];
const btn = document.querySelector('.btn');
btn.addEventListener('click', debounce(getNumber, number));
})()
</script>
</body>
</html>
这样咱们就摸清了手写 apply
。
七 bind
返回目录
7.1 原生 bind
返回目录
- MDN – bind
bind()
办法创立一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时应用。
-
语法:
function.bind(thisArg, arg1, arg2, ...)
thisArg
:调用绑定函数时作为this
参数传递给指标函数的值。arg1, arg2, ...
:当指标函数被调用时,被预置入绑定函数的参数列表中的参数。
- 返回值:一个原函数的拷贝,并领有指定的
this
值和初始参数
const module = {
x: 42,
getX: function() {return this.x;},
};
const unboundGetX = module.getX;
console.log(unboundGetX()); // undefined
// 谁调用指向谁,这里 unboundGetX = module.getX
// 让 getX 外面的 this 指向了 window
// 而 window 外面并没有 x 办法
// 当然,在后面加上 window.x = 43 就有了
const boundGetX = unboundGetX.bind(module);
console.log(boundGetX()); // 42
// 通过 bind,将 this 指向 module
7.2 手写 bind
返回目录
手写 bind
略微有点小简单,然而小伙伴们别慌,多读几遍能够摸清套路:
Function.prototype.myBind = function(context = globalThis) {
// 设置 fn 为调用 myCall 的办法
const fn = this;
// 获取该办法残余参数
const otherArg = [...arguments].slice(1);
// 设置返回的一个新办法
const result = function() {
// 获取返回办法体的参数
const resultArg = [...arguments];
// 如果是通过 new 调用的,绑定 this 为实例对象
if (this instanceof result) {fn.apply(this, otherArg.concat(resultArg));
} else { // 否则一般函数模式绑定 context
fn.apply(context, otherArg.concat(resultArg));
}
}
// 绑定原型链
result.prototype = Object.create(fn.prototype);
// 返回后果
return result;
};
this.a = 1;
const fn = function() {
this.a = 2;
console.log(this.a);
}
fn.myBind(fn);
fn();
八 题目
返回目录
通过下面的实际,小伙伴们应该对手写 call
、bind
、apply
有本人的了解了,上面看看这些题,试试挑战下。
8.1 this 指向问题 1
返回目录
var color = 'green';
var test4399 = {
color: 'blue',
getColor: function() {
var color = 'red';
console.log(this.color);
},
};
var getColor = test4399.getColor;
getColor(); // 输入什么?test4399.getColor(); // 输入什么?
答案:green
、blue
。
8.2 this 指向问题 2
返回目录
var myObject = {
foo: 'bar',
func: function() {
var self = this;
console.log(this.foo);
console.log(self.foo);
(function() {console.log(this.foo);
console.log(self.foo);
})()}
}
myObject.func();
程序输入什么?
- A:bar bar bar bar
- B:bar bar bar undefined
- C:bar bar undefined bar
- D:undefined bar undefined bar
答案:C
- 第一个
this.foo
输入bar
,因为以后this
指向对象myObject
。 - 第二个
self.foo
输入bar
,因为self
是this
的正本,同指向myObject
对象。 - 第三个
this.foo
输入undefined
,因为这个 IIFE(立刻执行函数表达式)中的this
指向window
。
4. 第四个 self.foo
输入 bar
,因为这个匿名函数所处的上下文中没有 self
,所以通过作用域链向上查找,从蕴含它的父函数中找到了指向 myObject
对象的 self
。
九 参考文献
返回目录
- [x] MDN – Arguments【浏览倡议:5min】
- [x] MDN – call【浏览倡议:5min】
- [x] MDN – apply【浏览倡议:5min】
- [x] MDN – bind【浏览倡议:5min】
- [x] 不必 call 和 apply 办法模仿实现 ES5 的 bind 办法【浏览倡议:1h】
- [x] JavaScript 深刻之 call 和 apply 的模仿实现【浏览倡议:20min】
- [x] this、apply、call、bind【浏览倡议:30min】
- [x] 面试官问:是否模仿实现 JS 的 call 和 apply 办法【浏览倡议:10min】
- [x] JavaScript 根底心法—— call apply bind【浏览倡议:20min】
- [x] 回味 JS 根底:call apply 与 bind【浏览倡议:10min】
jsliang 的文档库由 梁峻荣 采纳 常识共享 署名 - 非商业性应用 - 雷同形式共享 4.0 国内 许可协定 进行许可。<br/> 基于 https://github.com/LiangJunrong/document-library 上的作品创作。<br/> 本许可协定受权之外的应用权限能够从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处取得。