编程题
模拟实现系列(造火箭)
new 运算符
new 运算符原理
- 创建一个全新的对象
- 为新创建的对象添加
__proto__
属性并指向构造函数的原型对象 - 将新创建的对象作为函数调用的 this
- 如果构造函数没有返回对象类型,则返回新创建的对象
模拟实现 new 运算符
js
function myNew() {
// 获取构造函数
const Constructor = [].shift.call(arguments)
// 创建空对象并设置原型
const obj = Object.create(Constructor.prototype)
// 绑定 this 并执行构造函数
const result = Constructor.apply(obj, arguments)
// 返回构造函数显示返回的值或新对象
const type = typeof result
return result && (type === 'object' || type === 'function') ? result : obj
}
instanceof 运算符
instanceof 运算符原理
instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上
模拟实现 instanceof 运算符
js
function myInstanceof(left, right) {
// 取右边构造函数的 prototype 值
const prototype = right.prototype
// 取左边实例的 __proto__ 值
left = left.__proto__
while (true) {
// 当左边实例的 __proto__ 为 null 时返回 false
if (left === null) {
return false
}
// 判断左右两边的原型是否一致
if (left === prototype) {
return true
}
// 修改 __proto__
left = left.__proto__
}
}
Object.create()
Object.create()
Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__
模拟实现 Object.create()
js
function create(proto, properties) {
// 如果 proto 不是 null 或非原始包装对象,抛出 TypeError 异常
const type = typeof proto
if (type !== 'object' && type !== 'function') {
throw new TypeError('Object prototype may only be an Object or null: ' + proto)
}
function F() {}
// 将 proto 的原型设置为 F 的原型
F.prototype = proto
// 创建新对象
const result = new F()
// 兼容 null 的处理
if (proto === null) {
result.__proto__ = null
// OR Reflect.setPrototypeOf(result, null)
}
// 将 properties 的属性设置到新对象上
if (properties !== null && properties !== undefined) {
Object.defineProperties(result, properties)
}
return result
}
Function.prototype.call()
call()
方法使用一个指定的 this
值和单独给出的一个或多个参数来调用一个函数
ES6 模拟实现 Function.prototype.call()
js
/** ES6 实现 **/
Function.prototype.myCall = function (context, ...args) {
// 在非严格模式下,传入的 context 为 null 或 undefined 时会自动替换为全局对象
// 因此在判断时不能使用 context = context || window
if (context == null) {
context = window
}
// 原始值需要被 Object 包装成对象
else {
context = Object(context)
}
context.fn = this
// 获取 fn 函数调用的返回值
const result = context.fn(...args)
delete context.fn
return result
}
function get(params) {
console.log(this, params)
return '这是返回值'
}
const obj = {
name: 'maomao',
age: 18,
}
console.log(`call :>> `, get.call(obj, 'call'))
console.log(`myCall :>> `, get.myCall(obj, 'myCall'))
ES5 模拟实现 Function.prototype.call()
js
/** ES5 实现 **/
Function.prototype.myCall = function (context) {
// 在非严格模式下,传入的 context 为 null 或 undefined 时会自动替换为全局对象
// 因此在判断时不能使用 context = context || window
if (context == null) {
context = window
}
// 原始值需要被 Object 包装成对象
else {
context = Object(context)
}
// 获取调用 call 的函数
context.fn = this
// 获取传入的参数
var args = []
// arguments 是类数组对象,可以使用 for 循环
for (var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']')
}
/**
* 获取 fn 函数调用的返回值
* 此时 args 为 ['arguments[1]', 'arguments[2]', 'arguments[3]']
* 但在执行时 args 会自动调用 Array.toString() 转化为 context.fn(arguments[1], arguments[2], arguments[3])
**/
var result = eval('context.fn(' + args + ')')
// 删除 fn 函数
delete context.fn
// 将 fn 函数的返回值返回
return result
}
Function.prototype.apply()
apply()
方法调用一个具有给定 this
值的函数,以及以一个数组(或类数组对象)的形式提供的参数
ES6 模拟实现 Function.prototype.apply()
js
/** ES6 实现 **/
Function.prototype.myApply = function (context, arr) {
// 在非严格模式下,传入的 context 为 null 或 undefined 时会自动替换为全局对象
// 因此在判断时不能使用 context = context || window
if (context == null) {
context = window
}
// 原始值需要被 Object 包装成对象
else {
context = Object(context)
}
context.fn = this
// 获取函数调用的返回值
const result = arr ? context.fn(...arr) : context.fn()
delete context.fn
return result
}
function get(params) {
console.log(this, params)
return '这是返回值'
}
const obj = {
name: 'maomao',
age: 18,
}
console.log(`apply :>> `, get.apply(obj, ['apply']))
console.log(`myApply :>> `, get.myApply(obj, ['myApply']))
ES5 模拟实现 Function.prototype.apply()
js
/** ES5 实现 **/
Function.prototype.myApply = function (context, arr) {
// 在非严格模式下,传入的 context 为 null 或 undefined 时会自动替换为全局对象
// 因此在判断时不能使用 context = context || window
if (context == null) {
context = window
}
// 原始值需要被 Object 包装成对象
else {
context = Object(context)
}
// 获取调用 apply 的函数
context.fn = this
// 获取 fn 函数调用的返回值
var result
if (arr) {
// 获取传入的参数
var args = []
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']')
}
result = eval('context.fn(' + args + ')')
}
// 没有参数直接调用
else {
result = context.fn()
}
// 删除 fn 函数
delete context.fn
// 将 fn 的返回值返回
return result
}
Function.prototype.bind()
bind()
方法创建一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其余参数将作为新函数的参数供调用时使用。
ES6 模拟实现 Function.prototype.bind()
js
/** ES6 实现 **/
Function.prototype.myBind = function (context, ...args) {
if (typeof this !== 'function') {
throw new TypeError('not a function')
}
const self = this
return function F(...fArgs) {
const params = [...args, ...fArgs]
// 当作为构造函数时
if (this instanceof F) {
return new self(...params)
}
// 当作为普通函数时,将函数的 this 指向 context
return self.apply(context, params)
}
}
ES5 模拟实现 Function.prototype.bind()
js
/** ES5 实现 **/
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new Error('not a function')
}
// 获取调用 bind 的函数
var self = this
// 获取除了 thisArg 外的剩余参数(第二个到最后一个)
var args = Array.prototype.slice.call(arguments, 1)
var fNOP = function () {}
var fBound = function () {
// 获取返回函数的参数
var bindArgs = Array.prototype.slice.call(arguments)
return self.apply(
// 当作为构造函数时,将绑定函数的 this 实例指向实例
// 当作为普通函数时,将绑定函数的 this 指向 context
this instanceof fNOP ? this : context,
args.concat(bindArgs),
)
}
// 存在原型时,修改返回函数的 prototype 为绑定函数的 prototype,使实例可以继承绑定函数原型中的值
var prototype = self.prototype
if (prototype) {
fNOP.prototype = prototype
}
fBound.prototype = new fNOP()
return fBound
}
工具方法系列
debounce 函数防抖
函数防抖
作用: 一个函数在一段时间内多次触发都只执行最后一次
原理: 利用定时器,在函数第一次执行时设定一个定时器,再次调用时如果已经设定过定时器就清空之前的定时器并设定一个新的定时器,当定时器结束后执行传入的回调函数
应用: 搜索输入框获取用户输入的联想结果
实现防抖函数
js
function debounce(fn, wait) {
// 通过闭包缓存定时器 id
let timer = null
return function (...args) {
// 如果定时器已经存在,清除定时器
if (timer) {
clearTimeout(timer)
timer = null
}
// 设定定时器,定时器结束后执行传入的回调函数 fn
timer = setTimeout(() => {
fn.apply(this, args)
}, wait)
}
}
throttle 函数节流
函数节流
作用: 函数节流指指的是在一段时间内只允许函数执行一次 (例如 3
秒执行一次那么在函数第一次调用后的 3
秒内后面的函数调用将被忽略)
原理: 利用时间戳来判断,记录上次执行的时间戳,在每次触发事件时判断当前时间是否大于上次执行的时间 + 设置的间隔 ,如果是则执行回调并更新上次执行的时间戳
应用: 降低 scroll resize
事件的触发频率
实现节流函数
js
function throttle(fn, wait) {
// 通过闭包缓存上一次的调用时间 (默认为 0)
let lastCallTime = 0
return function () {
const now = Date.now()
// 判断当前调用时间和上次调用时间的差值是否大于 wait
if (now - lastCallTime >= wait) {
// 更新调用时间
lastCallTime = now
// 执行回调函数
fn.apply(this, arguments)
}
}
}