前言 不知道今年大家有没有感受到来自互联网的“寒气”,至少我是感受到了,面试的时候手写代码时很常见很常见的事情了。有时候没遇到过还真一时半会写不出来,企业招聘的要求也是越来越高,尤其是一些大厂会对 JS 的功底有着更加苛刻的要求,所以学会手写常见的 JS 模块好像已经快变为一个基本技能了,也慢慢变为我们手写 webpack 手写 mini-vue 的一个 coding 基础 了。当然我们也不完全是为了去准备面试而去学习这些常见模块。死磕这些难啃的骨头之后,你会从中学到很多优秀的思想,对你的职业生涯也是很有帮助的。而且阅读代码本身就是一个很好的习惯,读懂并且理解写代码人的思维逻辑更加重要。 本文中涉及到的手写模块,大多数都是从网上的博客、面筋、牛客网以及自己的面试经验借鉴而来的。希望能对你有个帮助。 基础手写全排列(力扣原题)要求以数组的形式返回字符串参数的所有排列组合。 注意: - 字符串参数中的字符无重复且仅包含小写字母
- 返回的排列组合数组不区分顺序
- const _permute = string => {
- const result = []
- const map = new Map()
- const dfs = (path) => {
- if (path.length === string.length) {
- result.push(path)
- return
- }
- for (let i = 0; i < string.length; i++) {
- if (map.get(string[i])) continue
- map.set(string[i], true)
- path += string[i]
- dfs(path)
- path = path.substring(0, path.length - 1)
- map.set(string[i], false)
- }
- }
- dfs('')
- return result
- }
- console.log(_permute('abc')) // [ 'abc', 'acb', 'bac', 'bca', 'cab', 'cba' ]
复制代码 instanceof- 如果 target 为基本数据类型直接返回 false
- 判断 Fn.prototype 是否在 target 的隐式原型链上
- const _instanceof = (target, Fn) => {
- if ((typeof target !== 'object' && typeof target !== 'function') || target === null)
- return false
-
- let proto = target.__proto__
- while (true) {
- if (proto === null) return false
- if (proto === Fn.prototype) return true
- proto = proto.__proto__
- }
- }
- function A() {}
- const a = new A()
- console.log(_instanceof(a, A)) // true
- console.log(_instanceof(1, A)) // false
复制代码
Array.prototype.map- map 中的 exc 接受三个参数,分别是: 元素值、元素下标和原数组
- map 返回的是一个新的数组,地址不一样
- // 这里不能直接使用箭头函数,否则无法访问到 this
- Array.prototype._map = function (exc) {
- const result = []
- this.forEach((item, index, arr) => {
- result[index] = exc(item, index, arr)
- })
- return result
- }
- const a = new Array(2).fill(2)
- console.log(a.map((item, index, arr) => item * index + 1)) // [1,3]
- console.log(a._map((item, index, arr) => item * index + 1))// [1,3]
复制代码
Array.prototype.filter- filter 中的 exc 接受三个参数,与map一致,主要实现的是数组的过滤功能,会根据 exc 函数的返回值来判断是否“留下”该值。
- filter 返回的是一个新的数组,地址不一致。
- Array.prototype._filter = function (exc) {
- const result = []
- this.forEach((item, index, arr) => {
- if (exc(item, index, arr)) {
- result.push(item)
- }
- })
- return result
- }
- const b = [1, 3, 4, 5, 6, 2, 5, 1, 8, 20]
- console.log(b._filter(item => item % 2 === 0)) // [ 4, 6, 2, 8, 20 ]
复制代码
Array.prototype.reduce- reduce 接受两个参数,第一个为 exc 函数,第二个为初始值,如果不传默认为 0
- reduce 最终会返回一个值,当然不一定是 Number 类型的,取决于你是怎么计算的,每次的计算结果都会作为下次 exc 中的第一个参数
- Array.prototype._reduce = function (exc, initial = 0) {
- let result = initial
- this.forEach((item) => {
- result = exc(result, item)
- })
- return result
- }
- console.log(b.reduce((pre, cur) => pre + cur, 0)) // 55
- console.log(b._reduce((pre, cur) => pre + cur, 0)) // 55
复制代码 Object.create Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)。 - Object.prototype._create = function (proto) {
- const Fn = function () { }
- Fn.prototype = proto
- return new Fn()
- }
- function A() { }
- const obj = Object.create(A)
- const obj2 = Object._create(A)
- console.log(obj.__proto__ === A) // true
- console.log(obj.__proto__ === A) // true
复制代码 Function.prototype.callcall() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。 - Function.prototype._call = function (ctx, ...args) {
- // 如果不为空,则需要进行对象包装
- const o = ctx == undefined ? window : Object(ctx)
- // 给 ctx 添加独一无二的属性
- const key = Symbol()
- o[key] = this
- // 执行函数,得到返回结果
- const result = o[key](...args)
- // 删除该属性
- delete o[key]
- return result
- }
- const obj = {
- name: '11',
- fun() {
- console.log(this.name)
- }
- }
- const obj2 = { name: '22' }
- obj.fun() // 11
- obj.fun.call(obj2) // 22
- obj.fun._call(obj2) // 22
复制代码 Function.prototype.bindbind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。 - const obj = {
- name: '11',
- fun() {
- console.log(this.name)
- }
- }
- Function.prototype._bind = function (ctx, ...args) {
- // 获取函数体
- const _self = this
- // 用一个新函数包裹,避免立即执行
- const bindFn = (...reset) => {
- return _self.call(ctx, ...args, ...reset)
- }
- return bindFn
- }
- const obj2 = { name: '22' }
- obj.fun() // 11
- const fn = obj.fun.bind(obj2)
- const fn2 = obj.fun._bind(obj2)
- fn() // 22
- fn2() // 22
复制代码 New 关键字new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。 - const _new = function(constructor) {
- // 创建一个空对象
- const obj = {}
- // 原型链挂载
- obj.__proto__ = constructor.prototype;
- // 将obj 复制给构造体中的 this,并且返回结果
- const result = constructor.call(obj)
- // 如果返回对象不为一个对象则直接返回刚才创建的对象
- return typeof result === 'object' && result !== null ? : result : obj
- }
复制代码 浅拷贝
- const _shallowClone = target => {
- // 基本数据类型直接返回
- if (typeof target === 'object' && target !== null) {
- // 获取target 的构造体
- const constructor = target.constructor
- // 如果构造体为以下几种类型直接返回
- if (/^(Function|RegExp|Date|Map|Set)$/i.test(constructor.name)) return target
- // 判断是否是一个数组
- const cloneTarget = Array.isArray(target) ? [] : {}
- for (prop in target) {
- // 只拷贝其自身的属性
- if (target.hasOwnProperty(prop)) {
- cloneTarget[prop] = target[prop]
- }
- }
- return cloneTarget
- } else {
- return target
- }
- }
复制代码 深拷贝实现思路和浅拷贝一致,只不过需要注意几点 - 函数 正则 日期 ES6新对象 等不是直接返回其地址,而是重新创建
- 需要避免出现循环引用的情况
- const _completeDeepClone = (target, map = new WeakMap()) => {
- // 基本数据类型,直接返回
- if (typeof target !== 'object' || target === null) return target
- // 函数 正则 日期 ES6新对象,执行构造题,返回新的对象
- const constructor = target.constructor
- if (/^(Function|RegExp|Date|Map|Set)$/i.test(constructor.name)) return new constructor(target)
- // map标记每一个出现过的属性,避免循环引用
- if (map.get(target)) return map.get(target)
- map.set(target, true)
- const cloneTarget = Array.isArray(target) ? [] : {}
- for (prop in target) {
- if (target.hasOwnProperty(prop)) {
- cloneTarget[prop] = _completeDeepClone(target[prop], map)
- }
- }
- return cloneTarget
- }
复制代码
|