高频手写题
new
js
function New(fn, ...args) {
// 1. 创建一个新对象并将新对象的隐式原型(obj.__proto__)指向构造函数的显示原型(fn.prototype)
const obj = Object.create(fn.prototype)
// 2. 将构造函数的this指向新对象
const res = fn.call(obj, ...args)
/**
* 3. 构造函数本身如果返回的是对象的话,则返回的就是此对象
* 如果返回值不是对象的话,则返回对应的obj
*/
return res instanceof Object ? res : obj
}
call
js
Function.prototype.Call = function(context = window, ...args) {
// 1. 获取传入对象的上下文
const object = context
// 2. 使用临时属性fn将构造函数挂载在传入的对象上
object.fn = this
// 3. 在传入的对象下执行构造函数,将构造函数的属性和方法赋值给传入的对象
const res = object.fn(...args)
// 4. 删除临时属性
delete object.fn
// 5. 返回结果
return res
}
apply
js
Function.prototype.Apply = function(context = window, args) {
// 1. 获取传入对象的上下文
const object = context
// 2. 使用临时属性fn将构造函数挂载在传入的对象上
object.fn = this
// 3. 在传入的对象下执行构造函数,将构造函数的属性和方法赋值给传入的对象
const res = args ? object.fn(...args) : object.fn()
// 4. 删除临时属性
delete object.fn
// 5. 返回结果
return res
}
bind
js
Function.prototype.bind = function(context = window, ...args) {
// 定义中转构造函数,用于通过原型连接绑定后的函数和调用bind的函数
let F = function() {},
// 记录调用函数,生成闭包,用于返回函数被调用时执行
self = this,
// 定义返回(绑定)函数
bound = function() {
// 返回函数后仍可接收参数 合并参数,绑定时和调用时分别传入的
let finalArgs = [...args, ...arguments]
/**
* 改变作用域,注:aplly/call是立即执行函数,即绑定会直接调用
* 这里之所以要使用instanceof做判断,是要区分是不是new xxx()调用的bind方法
* bind返回的新函数也能使用new操作符创建对象:这种行为就像把原函数作为构造器,提供的this值被忽略,同时调动时* 的参数被提供给模拟函数。
*/
return self.call((this instanceof bound ? this : context), ...finalArgs)
}
/**
* 直接使用bound.prototype = self.prototype有一个缺点,直接修改bound.prototype的时候,也会直接修改self.prototype
* 所以用一个空对象作为中介,将bound.prototype赋值为空对象的实例(原型式继承)
*/
// 将调用函数的原型赋值到中转函数的原型上
F.prototype = self.prototype
// 通过原型的方式继承调用函数的原型
bound.prototype = new F()
return bound
}
深拷贝
js
//判断是否是对象类型
function isObject(value) {
const valueType = typeof value
return (value !== null) && (valueType === "object" || valueType === "function")
}
function deepClone(originValue, map = new WeakMap()) {
//一、进行判断
// 判断是否是一个Set类型
if (originValue instanceof Set) {
return new Set([...originValue])
}
// 判断是否是一个Map类型
if (originValue instanceof Map) {
return new Map([...originValue])
}
// 判断如果是Symbol的value, 那么创建一个新的Symbol
if (typeof originValue === "symbol") {
return Symbol(originValue.description)
}
// 判断如果是函数类型, 那么直接使用同一个函数
if (typeof originValue === "function") {
return originValue
}
// 判断传入的originValue是否是一个对象类型
if (!isObject(originValue)) {
return originValue
}
//解决循环引用
if (map.has(originValue)) {
return map.get(originValue)
}
// 判断传入的对象是数组, 还是对象
const newObject = Array.isArray(originValue) ? []: {}
map.set(originValue, newObject)//解决循环引用
//二、值处理
for (const key in originValue) {
newObject[key] = deepClone(originValue[key], map)
}
// 对Symbol的key进行特殊的处理
const symbolKeys = Object.getOwnPropertySymbols(originValue)
for (const sKey of symbolKeys) {
newObject[sKey] = deepClone(originValue[sKey], map)
}
//三、返回新对象
return newObject
}
柯里化
固定参数
jsfunction curry(fn, ...args) { /** * fn.length: * 1. 函数的length是js函数对象的一个属性,函数的length代表形参的个数(即有多少必传参数) * 2. 形参的数量不包括剩余参数的个数,仅包括“第一个具有默认值之前的参数个数” */ return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args); } function add(a, b, c) { return a + b + c } const addCurry = curry(add) console.log(addCurry(1)(2)(3)) // 6 console.log(addCurry(1, 2)(4)) // 7
不固定参数
显示代码
jsfunction add() { // 第一次执行时,定义一个数组专门用来存储所有的参数 let _args = Array.prototype.slice.call(arguments); // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值 let _adder = function() { _args.push(...arguments); if (arguments.length === 0) { return _args.reduce(function(a, b) { return a + b; }); } return _adder; }; // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回 // _adder.toString = function() { // return _args.reduce(function(a, b) { // return a + b; // }); // } return _adder; } console.log(add(1)(2)(3)()) // 6 console.log(add(1, 2, 3)(4)()) // 10 // 利用隐式转换 // console.log(add(1)(2)(3) == 6) // true