怎样搭建网站视频教程,公司网站域名价格,河北百度seo点击软件,那家建设网站p2p公司最好前言
古人学问无遗力#xff0c;少壮工夫老始成。纸上得来终觉浅#xff0c;绝知此事要躬行。看懂一道算法题很快,但我们必须将这道题的思路理清、手写出来。
三道js手写题的思路和代码实现
数组扁平化
演示效果
将[1, [1, 2], [1, [2]]] 变成 [1, 1, 2, 1, 2]
第一种少壮工夫老始成。纸上得来终觉浅绝知此事要躬行。看懂一道算法题很快,但我们必须将这道题的思路理清、手写出来。
三道js手写题的思路和代码实现
数组扁平化
演示效果
将[1, [1, 2], [1, [2]]] 变成 [1, 1, 2, 1, 2]
第一种 直接使用.flat
console.log([1, [1,2],[1,[2]]].flat(3));可以将多维数组降维传的参数是多少就降多少维一般直接传参数为 Infinity(简单粗暴) 第二种: 递归方法的方法 借用数组的API完成
(1)
function flattten(arr) {var result [];for(var i 0, len arr.length; i len; i) {if(Array.isArray(arr[i])) { // Array.isArray 判断是否为数组result result.concat(flattten(arr[i])) // concat() 方法用于连接两个或多个数组。} else {result.push(arr[i])}}return result;
}(2)
function flatten(arr) {return arr.reduce((pre, cur) {return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);}, []);
}第四种: some …(扩展运算符) .concat
function flattten(arr) {// some() 方法用于检测数组中的元素是否满足指定条件函数提供。// some() 方法会依次执行数组的每个元素// 如果有一个元素满足条件则表达式返回true , 剩余的元素不会再执行检测。// 如果没有满足条件的元素则返回false。while(arr.some(item Array.isArray(item))) {console.log(arr)arr [].concat(...arr)// ... 会将多维数组降维一层}return arr
}第五种: 将多维数组转换成字符串在进行操作
(1)
function flatten(arr) {let str arr.toString();str str.replace(/(\[|\])/g, ).split(,).map(Number)return str;
}/([|])/g 正则表达式 () 代表一个分组, \是转义字符(因为正则表达式规则中有 [ 和 ]的语法, 用\就可以让规则忽略[和]) /g 为全局匹配, 只要遇到了[ 和 ], 就用’这个来代替。replace() 方法用于在字符串中用一些字符替换另一些字符或替换一个与正则表达式匹配的子串。 (2)
function flatten(arr) {let result arr.toString();result result.replace(/(\[|\])/g, );result [ result ];result JSON.parse(result);// JSON.parse()可以把JSON规则的字符串转换为JSONObjectreturn result;
}深浅拷贝
浅拷贝的实现
明白浅拷贝的局限性: 只能拷贝一层对象。 如果存在对象的嵌套, 那么浅拷贝将无能为力对于基础数据类型做一个最基本的拷贝对引用类型开辟一个新的存储, 并拷贝一层对象属性
参考 前端进阶面试题详细解答
function deepClone(target) {if(typeof target object target ! null) {// 判断是数组还是对象const targetclone Array.isArray(target)? []:{}// 键值是否存在for(let prop in target) {if(target.hasOwnProperty(prop)) {// hasOwnProperty() 方法不会检测对象的原型链// 只会检测当前对象本身只有当前对象本身存在该属性时才返回 true。targetclone[prop] (typeof target[prop] object)?deepClone(target[prop]):target[prop]}}return targetclone;} else {return target;}
}let arr1 [ 1, 2, { val: 4, xdm: { dd: 99 } } ];let str shallowerClone(arr1)console.log(arr1, arr1)console.log(str, str)str.push({mo: 兄弟们})console.log(str.push-----------)console.log(arr1, arr1)console.log(str, str push)深拷贝的最终版 ,
深拷贝的思路:
对于日期和正则的类型时, 进行处理 new一个新的对a: { val: a } 这种循环引用时, 使用以weakMap进行巧妙处理使用Reflect.ownKeys返回一个由目标对象自身的属性键组成的数组对于剩下的拷贝类型为object和function但不是null进行递归操作,对于除了上述的类型外直接进行key的赋值操作。 细节处理:利用getOwnPropertyDescriptors返回指定对象所有自身属性非继承属性的描述对象将得到的属性利用Object.create进行继承原型链对于a: { val: a} 循环引用使用weakMap.set和get进行处理。 实现代码
const isComplexDataType obj (typeof obj object || typeof obj function) (obj ! null)
const deepClone function (obj, hash new WeakMap()) {if (obj.constructor Date) return new Date(obj) // 日期对象直接返回一个新的日期对象if (obj.constructor RegExp)return new RegExp(obj) //正则对象直接返回一个新的正则对象//如果循环引用了就用 weakMap 来解决if (hash.has(obj)) return hash.get(obj)let allDesc Object.getOwnPropertyDescriptors(obj)//遍历传入参数所有键的特性let cloneObj Object.create(Object.getPrototypeOf(obj), allDesc)//继承原型链hash.set(obj, cloneObj)for (let key of Reflect.ownKeys(obj)) { // 针对能够遍历对象的不可枚举属性以及 Symbol 类型我们可以使用 Reflect.ownKeys 方法cloneObj[key] (isComplexDataType(obj[key]) typeof obj[key] ! function) ? deepClone(obj[key], hash) : obj[key]// typeof obj[key] ! function)}return cloneObj
}检测代码
let obj {num: 0,str: ,boolean: true,unf: undefined,nul: null,obj: { name: 我是一个对象, id: 1 },arr: [0, 1, 2],func: function () { console.log(我是一个函数) },date: new Date(0),reg: new RegExp(/我是一个正则/ig),[Symbol(1)]: 1,
};
Object.defineProperty(obj, innumerable, {enumerable: false, value: 不可枚举属性 }
);
obj Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop obj // 设置loop成循环引用的属性
let cloneObj deepClone(obj)
cloneObj.arr.push(4)
console.log(obj, obj)
console.log(cloneObj, cloneObj)
console.log(cloneObj.func)实现了对象的循环应用的拷贝
对于上述代码进行说明:
Object.getOwnPropertyDescriptors 返回指定对象所有自身属性非继承属性的描述对象。可以去这里了解更多api Object.create()方法创建一个新对象使用现有的对象来提供新创建的对象的__proto__ Object.create 如果该参数被指定且不为 undefined该传入对象的自有可枚举属性(即其自身定义的属性而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
const person {isHuman: false,
};
const me Object.create(person);
console.log(me.__proto__ person); // trueObject.getPrototypeOf 方法返回指定对象的原型(内部[[Prototype]]属性的值)继承原型链
WeakMap 对象是一组键值对的集合其中的键是弱引用对象而值可以是任意。因为 WeakMap 是弱引用类型可以有效防止内存泄漏,作为检测循环引用很有帮助如果存在循环则引用直接返回 WeakMap 存储的值。可以从这里了解更多的WeapMap和Map的区别 Reflect.ownKeys Object.getOwnPropertyNames(target) contact (Object.getOwnPropertySymbols(target)。 Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名包括不可枚举属性但不包括Symbol值作为名称的属性组成的数组。 Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组
事件总线发布订阅模式
原理
事件总线
是发布/订阅模式的实现其中发布者发布数据 并且订阅者可以监听这些数据并基于这些数据作出处理。 这使发布者与订阅者松耦合。发布者将数据事件发布到事件总线 总线负责将它们发送给订阅者
on 或 addListener(event, listenr)
就是为指定事件添加一个监听器到监听数组的尾部。
off 或 removeListener(event, listenr)
移除指定事件的某个监听器, 监听器必须是该事件已经注册过的监听事件。
emit(event, [arg1], [arg2] …)
按照参数的顺序执行每个监听器, 如果事件有注册监听返回true, 否则返回false。 利用Node.js来了解 事件总线
var events require(events);
var eventEmitter new events.EventEmitter();
eventEmitter.on(say, function(name) {console.log(Hello, name);
})
eventEmitter.emit(say, 若离老师);
function helloA(name) {console.log(helloAAAAAAA, name)
}function helloB(name) {console.log(helloBBBBBBB, name)
}eventEmitter.on(say, helloA)
eventEmitter.on(say, helloB)
eventEmitter.emit(say, 若离老师)
eventEmitter.off(say, helloB);
eventEmitter.emit(say, 若离老师)新定义的eventEmitter 是接收 events.EventEmitter 模块 new 之后返回的一个实例eventEmitter 的 emit 方法发出 say 事件通过 eventEmitter 的 on 方法监听从而执行相应的函数。当触发off时, 将say事件上的响应函数删除。
on实现代码:
on的实现思路
对于on为指定事件添加一个监听器: 形式为{“say”: [ {listener:(函数) , once:(false or true)}, {}, {} ] }
参数有两个(name, fn)name为指定事件, fn是一个回调函数对于fn进行判断: 是否不存在、是否是合法的(为function)、判断不能重复添加事件 on的如下代码
function EventEmitter() {this.__events {}
}// 判断是否是合法的 listener
function isValidListener(listener) {if (typeof listener function) {return true;} else if (listener typeof listener object) {// listener 作为自定义事件的回调必须是一个函数// 另外判断是否是object这块递归的去找对象中是否还存在函数如果不是函数// 自定义事件没有回调肯定是不行的return isValidListener(listener.listener);} else {return false;}
}
// 顾名思义判断新增自定义事件是否存在
function indexOf(array, item) {var result -1item typeof item object ? item.listener : item;for (var i 0, len array.length; i len; i) {if (array[i].listener item) {result i;break;}}return result;
}
EventEmitter.prototype.on function(eventName, listener){if (!eventName || !listener) return;// 判断回调的 listener 是否为函数if (!isValidListener(listener)) {throw new TypeError(listener must be a function);}let events this.__events;console.log(events)// var listeners events[eventName] events[eventName] events[eventName] || [];events[eventName] events[eventName] || [];let listeners events[eventName]// listenerIsWrapped 表示是否已经封装了{listener: listener,once: false}let listenerIsWrapped (typeof listener object);// 不重复添加事件判断是否有一样的if (indexOf(listeners, listener) -1) {listeners.push(listenerIsWrapped ? listener : {listener: listener,once: false});}return this;// this指向EventEmitter返回的是实际调用这个方法的实例化对象
};连等赋值操作的坑: A B C 其中执行的顺序为 BC A B emit的代码实现
emit的思路
从this._events中拿出相应的监听事件进行执行(注意多个事件的执行)
emit的如下代码
EventEmitter.prototype.emit function(eventName,...args) {// 直接通过内部对象获取对应自定义事件的回调函数let listeners this.__events[eventName];if (!listeners) return;// 需要考虑多个 listener 的情况for (let i 0; i listeners.length; i) { let listener listeners[i];if (listener) {listener.listener.call(this, ...args || []);// 给 listener 中 once 为 true 的进行特殊处理if (listener.once) {this.off(eventName, listener.listener)}}}return this;
};listener.listener.call(this, …args || []); 将this绑定到listener.listener然后进行执行相应的函数。 例如:当执行到fn1时 fn1.call(this, name, age)。相当于执行函数fn1()。 off的代码实现
off的思路
将监听事件上相应的函数进行删除
off的代码如下
EventEmitter.prototype.off function(eventName, listener) {// 进行基础的判断let listeners this.__events[eventName];let index -1;if(!listeners) return;for(let i 0; i listeners.length; i) {if(listeners[i] listeners[i].listener listener) {index i;break;}}if(index ! -1) {listeners.splice(index, 1, null);}return this;}发布订阅模式的检测代码
let eventBus new EventEmitter()
let fn1 function(name, age) {console.log(${name} ${age})
}
let fn2 function(name, age) {console.log(hello, ${name} ${age})
}
let fn3 function(name, age) {console.log(hello myname is, ${name} ${age})
}
eventBus.on(say, fn1)
eventBus.on(say, fn2)
eventBus.on(say, fn3)
eventBus.emit(say,布兰, 12)
eventBus.off(say, fn1)
console.log(使用off删除了say事件上的fn1函数-------)
eventBus.emit(say,布兰, 12)