编写常见的js题
// 菜单/树类型转化 var arr = [ {id: 1, name: '部门1', pid: 0}, {id: 2, name: '部门2', pid: 1}, {id: 3, name: '部门3', pid: 1}, {id: 4, name: '部门4', pid: 3}, {id: 5, name: '部门5', pid: 4}, ] /** * 递归查找,获取children */ var getChildren = (data, result, pid) => { for (const item of data) { if (item.pid === pid) { const newItem = {...item, children: []}; result.push(newItem); getChildren(data, newItem.children, item.id); } } } /** * 转换方法 */ var arrayToTree = (data, pid) => { const result = []; getChildren(data, result, pid) return result; } ---------------------------------- var arr = [ {id: 1, name: '部门1', pid: 0}, {id: 2, name: '部门2', pid: 1}, {id: 3, name: '部门3', pid: 1}, {id: 4, name: '部门4', pid: 3}, {id: 5, name: '部门5', pid: 4}, {id: 6, name: '部门xxxxx', pid: 5}, ] function arrayToTree(items) { const r = []; // 存放结果集 const obj = {}; // for (const item of items) { obj[item.id] = {...item, children: []} } for (const {id, pid} of items) { const treeItem = obj[id]; pid === 0 ? r.push(treeItem) : obj[pid].children.push(treeItem) } return r; } console.log('arrayToTree(arr)', JSON.stringify(arrayToTree(arr), null, 2))
// var a = { // i:0, // toString() { // return ++this.i // } // } // var a = [1,2,3] // a.toString = a.shift var num=1; Object.defineProperty(window,'a',{ get:function(){ return num++; } }) if(a == 1 && a==2 & a==3){ console.log('ok') }
// 手写深度克隆 function deepClone(obj) { // 过滤特殊情况 if (obj === null) return null if (typeof obj !== 'object') return obj if (obj instanceof RegExp) return new RegExp(obj) if (obj instanceof Date) return new Date(obj) // 不直接创建空对象目的:克隆的结果和之前保持相同的所属类 let newObj = new obj.constructor for(let key in obj){ if(obj.hasOwnProperty(key)){ newObj[key] = deepClone(obj[key]) } }
return newObj
}
// 惰性函数 /* * DOM2事件绑定 * 元素.addEventListener() * 元素.attachEvent() * */ function emit(element, type, func) { if (element.addEventListener) { emit = function (element, type, func) { element.addEventListener(type, func, false) } } else if (element.attachEvent) { emit = function (element, type, func) { element.attachEvent('on' + type, func) } } else { emit = function (element, type, func) { element['on' + type] = func } } emit(element, type, func) } emit(box, 'click', fn1) emit(box, 'click', fn2) /* * * 未优化的代码 * * */ function emitDemo(element, type, func) { if (element.addEventListener) { element.addEventListener(type, func, false) } else if (element.attachEvent) { element.attachEvent(type, func, false) } else { element['on' + type] = func } }
// 发布订阅模式 (function () { const hasOwn = Object.prototype.hasOwnProperty class Sub { $pond = {} // 向事件池追加方法 $on (type, func) { // 每一次增加的时候,首选验证是否存在这个自定义事件,存在则把方法加入到指定事件类型的容器末尾 // 但是需要做去重处理,不存在则创建一个自定义事件,属性值是空数组,再把方法加入到数组中 let $pond = this.$pond, arr = null !hasOwn.call($pond, type) ? $pond[type] = [] : null arr = $pond[type] !arr.includes(func) ? arr.push(func) : null } // 通知事件池的方法执行 $emit (type, ...params) { let $pond = this.$pond, arr = $pond[type] if (!arr) return for (let i = 0; i < arr.length; i++){ let itemFunc = arr[i] if (typeof itemFunc !== 'function') { // 此时把非函数的项都移除掉 arr.splice(i, 1) i-- continue } itemFunc(...params) } } // 从事件池中移除方法 $off (type, func) { let $pond = this.$pond, arr = $pond[type] for (let i = 0; i < arr.length; i++) { if ( arr[i] === func) { // splice删除改变原来的数据,原始数组中移除这些项,移除项后面的每一项都会向前改变索引 // 为了防止数据结构塌陷问题,我们在移除时候应该不去改变数组的结构,而是把当前项赋值为null // arr.splice(i, 1) arr[i] = null break } } } } // 暴露给全局的用的API window.subscribe = function () { return new Sub } })()
// call
/*
* 核心原理:给context设置一个属性(属性名尽可能保持唯一性,避免我们自己设置的属性修改默认对象中的结构,例如可以基于Symbol实现,
* 也可以创建一个时间戳名字),属性值一定是我们要执行的函数(也就是this,call中的this就是我们要操作的这个函数),接下来基于context.xxx()
* 成员访问执行方法,就可以把函数执行,并且改变里面的this(还可以把params中的信息传递给和这个函数即可)都处理完了,别忘记context设置的这个属性
* 删除掉
*
* 如果context是基本类型值,默认是不能设置属性的,此时我们需要把这个基本类型值修改为它对应的构造函数类型值
*
* */
(function () { function change(context, ...params) { context = context == null ? window : context let type = typeof context if (!/^(object|function)$/.test(type)) { context = /^(symbol|bigint)$/.test(type) ? Object(context) : new context.constructor(context) } let attr = Symbol('ATTR'), result context[attr] = this result = context[attr](...params) delete context[attr] return result } Function.prototype.change = change })() let obj = { name: 'zhufeng' } function func(x, y) { this.total = x + y return this } let res = func.change(obj, 100, 200) console.log('res -> ', res) // res => {name: 'zhufeng', total: 300}
// bind // 执行bind,bind中的this是要操作的函数,返回一个匿名函数给事件绑定或者其他内容,当事件触发的时候,首先执行的是匿名函数,此时匿名函数中的this和bind中this没有关系 Function.prototype._bind = function(context=window, ...params){ const _this = this return function anonymous(ev){ _this.apply(context, params.concat(ev)) } }
// 对象类型跟数字比较 先转字符串 -> Number() 最后比较 // var a = { // i: 1, // valueOf () { // return this.i++ // } // } // 用toString或者valueOf都可以,优先级valueOf > toString // var a = [1, 2, 3] // a.valueOf = a.shift let i = 0 Object.defineProperty(window, 'a', { get () { return ++i } }) if (a == 1 && a == 2 && a == 3) { console.log('ok 99999') }
// 惰性函数 function emit (element, type, func) { if (element.addEventListener) { emit = function (element, type, func) { element.addEventListener(type, func, false) } } else if (element.attachEvent) { emit = function (element, type, func) { element.attachEvent(type, func, false) } } } emit(box, 'click', fn1) emit(box, 'click', fn2)
// 手写new
function Dog(name){ this.name = name } Dog.prototype.bark = function(){ console.log('wangwang') } Dog.prototype.sayName = function(){ console.log('my name is' + this.name) } function _new(Func, ...args){ // 1.创建一个实例对象 对象.__proto__ === 类.prototype let obj = {} obj.__proto__ = Func.prototype // 2.把类当作普通函数执行 函数中的this指向实例对象 let r = Func.call(obj, ...args) // 3.看一下函数执行是否存在返回值,不存在或者返回的是值类型,则默认返回实例, // 如果返回的是引用数据类型则返回的不是实例而是自己写的引用类型 if(r !== null ) { return r } return r } var sanmao = _new(Dog, '三毛') sanmao.bark() sanmao.sayName() console.log(sanmao instanceof Dog)