面试 -- js
let var const?
- var会进行声明提升,没有局部作用域,并且可以重复声明
- let 不会进行声明提升,没有局部作用域,不能重复声明
- let const class声明的变量不属于顶层对象的属性(浏览器环境的window,Node环境的global),而是放在了script块作用域中,var,function声明的全局变量依旧是顶层对象的属性
- const 对于基本类型,不能修改值,对于引用类型,可以修改其内部属性值
- 可以使用
Object.freeze()与递归来冻结对象中的每一个值jsvar constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach( (key, i) => { if ( typeof obj[key] === 'object' ) { constantize( obj[key] ); } }); }; - 暂时性死区js
if (true) { // TDZ开始 tmp = 'abc'; // ReferenceError console.log(tmp); // ReferenceError let tmp; // TDZ结束 console.log(tmp); // undefined tmp = 123; console.log(tmp); // 123 } //typeOf也不是一个百分百安全的操作了 typeof x; // ReferenceError let x;
new
- 创建一个空对象
let obj1 = Object.create(null)没有__proto__,如果不用方法,效率更高let obj2 = {}
- 设置原型链
obj.__proto__ = XXX.prototype
- 改变this指向
let result = xxx.call(obj)
- 判断返回值
- 创建一个空对象
this(执行主体)指向问题
- 函数执行或者方法调用的时候,this指向指向他的调用者js
test() //严格模式下 this-->undefined 非严格模式下this-->window //node环境下指向gloabl let obj1 ={ name:'111', f1:function(){}, obj2:{ name:'222', f2:function(){} } } obj1.f1() //this-->obj1 obj1.obj2.f2()//this-->obj2 多层对象嵌套,指向离调用函数最近的对象 - 给当前元素的某个元素行为绑定方法,当事件行为触发,方法中的this就是当前元素本身
- 构造函数中的this指向实例对象
- 箭头函数的this
- 箭头函数没有this和arguments
- 不要作为对象内置函数,不能读取对象内其他值
- 不能作为构造函数
- 不能在动态上下文中绑定(dom监听)
- 无法通过 call、bind、apply 改变 this 指向
- 箭头函数的this在时就决定了,箭头函数的this与所在作用域的this指向一致
- js中只有全局作用域,函数作用域,块级作用域,箭头函数要看他所处的作用域
- 箭头函数没有this和arguments
- 可以通过
call/apply/bind改变this指向js//1.手撕call //原理:利用.调用改变this Function.prototype.call = function call(context, ...params) { let self = this, key = Symbol('key'), result context == null ? context = window : null; !/^(object|function)$/i.test(typeof context) ? context = Object( context) : null //包装对象,防止context是基本属性 context[key] = self //在context上加一个唯一属性,就是待调用的函数 result = context[key](...params) delete context[key] //删除此属性 return result //返回值 } //2.手撕bind Function.prototype.bind = function bind(context, ...params) { let self = this return function proxy(...args) { //执行proxy时可以再传的参数,如$event self.apply(context, [...params, ...args]) } } //鸭子类型 类数组-->数组 Array.prototype.slice.call(arguments)
- 函数执行或者方法调用的时候,this指向指向他的调用者
JS几大循环对比以及性能分析
for循环和while循环for用var声明的时候,for和while性能差不多,(不确定循环次数用while,确定用for)for用let声明的时候,for的性能更好,因为没有创造全局不释放的变量
for和forEachfor属于命令式语句,可以自己灵活管控循环过程break,continue,returnforEach属于封装好的函数语句,不好管控循环过程,return只能终止本次循环,thorw new Error()才能跳出整个循环,相对来说,for循环性能更好- 重写
forEachjsArray.prototype.forEach = function forEach(callBack, context) { let self = this, i = 0, len = self.length context = context == null ? window : context for (; i < len; i++) { typeof callBack === 'function' ? callBack.call(context, self[i], i) : null } }
for...in和for...offor...in性能很差,迭代当前对象中所有可枚举的属性,大部分私有属性,还有部分公有属性(出现在所属类的原型上的)都是可枚举的,所以他会查找到原型链上去。遍历的是key值。我们可以通过Object.getOwnPropertyDescriptors(obj)来查看属性的enumerable属性是不是true来判断是否可枚举(数组、字符串、对象)- 遍历的结果属性名为整数数字升序排序
- 无法遍历symbol属性
- 会遍历公有属性(添加到原型链上的)
for...of只能遍历可迭代的数据,遍历的是value值,我们可以通过[Symbol.iterator]来看是否有next函数判断是否可迭代(数组、部分类数组、字符串、set、map)- 代码实现js
arr[Symbol.iterator] = function () { let self = this, index = 0 return { next() { //必须具备next方法,执行一次next方法,拿到结构中的某一项的值 if (index > self.length - 1) { return { done: true, value: undefined } } return { done: false, value: self[index++] } } } } - 让类数组对象可以用
for..of:obj[Symbol.iterator] = Array.prototype[Symbol.iterator]
- 代码实现
异步遍历器
for await(let x of arr){}jslet b = Promise.resolve('11') let b = Promise.resolve('33') let c = Promise.resolve('6666') let arr = [a, b, c]; (async function () { for await (const x of arr) { console.log(x); } })(); //11 33 6666- 可以用来遍历一组
promise,promise.all也有类似效果
- 可以用来遍历一组
防抖和节流
debounce只执行最后一次,防止频繁操作- 应用场景:input输入 resize屏幕变化
jsfunction debounce(fn,delay){ let timer = null return function(){ if(timer) clearTimeOut(timer) timer = setTimeOut(()=>{ fn.call(this,arguments) timer = null },delay) } }throttle是有规律的执行- 应用场景:拖拽监听 scroll
jsfunction throttle(fn,delay){ let timer = null return function(){ if(timer) return timer = setTimeOut(()=>{ fn.call(this,arguments) timer = null },delay) } }
event Loop事件机制
- 同步代码,一行一行放在
Call Stack中执行 - 遇到异步,会先“记录”下代码,等待执行时机(setTimeOut,Ajax),时机到了,将之前记录的代码放入到
Callback Queue - 当
Call Stack为空(同步代码执行完毕),event loop开始工作 - event loop轮询查找
Callback Queue中是否有可执行的代码,如果有,将代码移动到Call Stack中执行 - event loop如果没有找到可以执行代码,则会继续轮询查找
- 延时队列:用于存放计时器到达后的回调任务,优先级「中」
- 交互队列:用于存放用户操作后产生的事件处理任务,优先级「高」
- 微队列:用户存放需要最快执行的任务,优先级「最高」
- 同步代码,一行一行放在
手写一个promise加载图片
jsconst op = new Promise((resolve, reject) => { const myImg = new Image() myImg.src = 'https://robohash.org/1' myImg.onload = () => resolve(myImg) myImg.onerror = () => reject('加载失败') }) op.then((value) => { document.body.appendChild(value) }).catch((e) => { console.log(e) })数据类型检测
typeof- 直接在计算机底层基于数据类型的值(二进制的前缀以及特点)进行检测
typeof null === 'object'对象存储在计算机中,都是以000开始的二进制存储,null也是,所以检测出来是对象typeof 普通对象/数组/正则/日期 === 'object'无法细致区分 只能区分除了null以外的基本类型和function
A instanceof B检测当前A实例是否属于这个B类- 底层机制,只要当前B类出现在A实例的原型链上,结果都是true
- 原型的指向是可以修改的,所以检测结果是不准确的
- 不能检测基本数据类型
- 手写instanceofjs
function insrance_of(example, classFunc) { let classFuncPrototype = classFunc.prototype, proto = Object.getPrototypeOf(example) while (true) { if (proto === null) return false if (proto === classFuncPrototype) return true proto = Object.getPrototypeOf(proto) } }
constructor- 比instanceof好用,支持基本类型,而且可以区分引用类型
- 但是constructor也是可以修改的,所以也不准确
arr.constructor === Array //true
Object.prototype.toString.call([value])- 标准检测数据类型的方法,返回当前实例所属类的信息
[object Null/Number/String/Boolean/Undefined/Symbol/Object/Array/RegExp/Date/Function]- JQ源码封装toType方法js
(function () { let class2Type = {} let toString = class2Type.toString; //Object.prototype.toString 注意这个分号,防止foreach报错 //设定数据类型的映射表 ['Boolean', 'Number', 'String', 'Function', 'Array', 'RegExp', 'Date', 'Object', 'Error', 'Symbol'].forEach( name => { class2Type[`[object ${name}]`] = name.toLowerCase() }); // console.log(class2Type) function toType(obj) { if (obj == null) return obj + '' //判断null 和 undefined return typeof obj === 'object' || typeof obj === 'function' ? class2Type[toString.call(obj)] || 'object' : typeof obj } window.toType = toType })()
深浅拷贝
- 浅拷贝:只能拷贝第一层的值,地址值也是拷贝js
//数组 newArr = [...arr] newArr = arr.concat([]) newArr = arr.slice() //对象 newObj = Object.assign({},obj) newObj = {...obj} let newObj = {} for(let key in obj){ newObj[key] = obj[key] //自己forin遍历不支持Symbol属性的处理 } let newObj = {} let keys = [...Object.keys(),...Object.getOwnPropertySymbols()] for(let key in obj){ newObj[key] = obj[key] }jsfunction deepClone(obj){ if(typeof obj !== 'object' || obj == null) return obj let result if(obj instanceof Array){ result = [] }else{ result ={} } for(let key in obj){ if(obj.hasOwnProperty(key)){ result[key] = deepClone(obj[key]) } } return result }
- 浅拷贝:只能拷贝第一层的值,地址值也是拷贝
对象合并
- 基于浅比较实现的对象合并js
Object.assign(options,params) - merge实现js
//A-->options中的key值 B-->target中的key值 //1. A & B都是原始值,B替换A即可 //2. A是对象 & B是原始值,抛出异常信息 //3. A是原始值 & B是对象,B替换A即可 //4. A & B都是对象,依次遍历B中每一项,重复merge判断过程 function merge(options, params = {}) { function isObj(value) { //判断是否为对象 return Object.prototype.toString.call() === '[object object]' } for (const key in params) { let isA = isObj(options[key]) let isB = isObj(params[key]) if (isA && !isB) throw new TypeError(`${key} in params must be object`) if (isA && isB) { options[key] = merge(options[key], params[key]) return } options[key] = params[key] return options } }
- 基于浅比较实现的对象合并
aop切面编程(埋点)
js//事件执行前定义 Function.prototype.before = function(beforefn){ if(typeof beforefn !== 'function') throw new TypeError('callback must be function') let _self = this return function proxy(...params){ beforefn.call(this,...params) return _self.call(this,...params) } } //事件执行后定义 Function.prototype.after = function(afterfn){ if(typeof afterfn !== 'function') throw new TypeError('callback must be function') let _self = this return function proxy(...params){ let res = _self.call(this,...params) afterfn.call(this,...params) return res } } //事件环绕(前+后)定义 Function.prototype.around = function(beforefn,afterfn){ if(typeof afterfn !== 'function') throw new TypeError('callback must be function') let _self = this return function proxy(...params){ beforefn.call(this,...params) let res = _self.call(this,...params) afterfn.call(this,...params) return res } } let func = ()=>{ //主要业务逻辑 } func.before(()=>{ console.log('====before====') }).after(()=>{ console.log('====after====') })js设计模式
设计模式就是一种思想,是用来规范编程的代码,使得代码更加整洁、清晰、维护性高、可拓展性高- 单例设计模式 && 命令模式:基于单独的实例,来管理某一个模块中的内容,实现模块之间的独立划分,也可以实现模块之间方法的相互调用js
var aModule = (function(){ var data = [] function bindHTML(){} function afunc(){} //window.afunc = afunc 1. 挂载到window上来实现暴露 return {afunc} //2. 通过闭包暴露 })() var bModule = (function(){ var data = [] function bindHTML(){} function bfunc(){} aModule.afunc() //单例模式 缺点:如果修改,修改的是闭包下的同一容器,这样会产生关联和影响 //window.afunc() 污染了全局变量 })() // AMD CMD ES6 Module //业务来讲:按照一定的顺序依次执行的方法,从而实现整个板块功能的开发 let searchModule = (function(){ function queryData(){} function bindHTML(){} function handler(){} return { init:function(){ //init相当于大脑,可以控制谁先执行,谁后执行 queryData() bindHTML() handler() } } })() searchModule.init() //命令模式 - 构造器模式(插件组件封装、类库封装)js
class Amodule{ constructor(){ this.arr = [] //this-->每个类的实例 } change(val){ //原型上的公共属性和方法 this.arr.push(val) } } - 工厂模式(Factory):帮助我们实现调用的切换,或者实现一些中转的处理js
function factory(options){ options = options || {} let {type,payload} = options if(type == 'array'){ //执行A逻辑 return } //执行B逻辑 } - 观察者模式(Observer--Subject)vue2.0响应式原理js
class Dep {// 发布者 - 目标 constructor () { this.subs = []// 记录所有的订阅者 } addSubs (sub) {// 把订阅者添加到 subs 中 if (sub && sub.update) { this.subs.push(sub) } } notify () {// 当事件发生,通知所有的订阅者,调用所有观察者的 update() 方法 this.subs.forEach(sub => { sub.update() }) } } class Watcher {// 订阅者 - 观察者 update () { // 当事件发生,由发布者调用所有观察者的 update() 方法 // 实现更新视图等功能 console.log('update'); } } // 测试 let dep = new Dep() let watcher = new Watcher() dep.addSubs(watcher) dep.notify() - 中介者模式(Mediator)
- 发布/订阅模式(publish/subscribe)
- 发布订阅模式中统一由调度中心进行处理,订阅者和发布者互不干扰。这样一方面实现了解耦,还有就是可以实现更细粒度的一些控制。比如发布者发布了很多消息,但是不想所有的订阅者都接收到,就可以在调度中心做一些处理,类似于权限控制之类的。还可以做一些节流操作
js//定义一家猎人工会 //主要功能包括任务发布大厅(topics),以及订阅任务(subscribe),发布任务(publish) let HunterUnion = { type: 'hunt', topics: Object.create(null), subscribe: function (topic, fn){ if(!this.topics[topic]){ this.topics[topic] = []; } this.topics[topic].push(fn); }, publish: function (topic, money){ if(!this.topics[topic]) return; for(let fn of this.topics[topic]){ fn(money) } } } //定义一个猎人类 //包括姓名,级别 function Hunter(name, level){ this.name = name this.level = level } //猎人可在猎人工会发布订阅任务 Hunter.prototype.subscribe = function (topic, fn){ console.log(this.level + '猎人' + this.name + '订阅了狩猎' + topic + '的任务') HunterUnion.subscribe(topic, fn) } Hunter.prototype.publish = function (topic, money){ console.log(this.level + '猎人' + this.name + '发布了狩猎' + topic + '的任务') HunterUnion.publish(topic, money) } //猎人工会走来了几个猎人 let hunterMing = new Hunter('小明', '黄金') let hunterJin = new Hunter('小金', '白银') let hunterZhang = new Hunter('小张', '黄金') let hunterPeter = new Hunter('Peter', '青铜') //小明,小金,小张分别订阅了狩猎tiger的任务 hunterMing.subscribe('tiger', function(money){ console.log('小明表示:' + (money > 200 ? '' : '不') + '接取任务') }) hunterJin.subscribe('tiger', function(money){ console.log('小金表示:接取任务') }) hunterZhang.subscribe('tiger', function(money){ console.log('小张表示:接取任务') }) //Peter订阅了狩猎sheep的任务 hunterPeter.subscribe('sheep', function(money){ console.log('Peter表示:接取任务') }) //Peter发布了狩猎tiger的任务 hunterPeter.publish('tiger', 198) //猎人们发布(发布者)或订阅(观察者/订阅者)任务都是通过猎
- 单例设计模式 && 命令模式:基于单独的实例,来管理某一个模块中的内容,实现模块之间的独立划分,也可以实现模块之间方法的相互调用
基于JS实现ajax并发请求的控制
1. 串行:请求是异步的,需要等待上一个请求成功,才能执行下一个请求
2. 并行:同时发多个请求,HTTP请求可以同时进行,但是JS是单线程,操作都是一步一步来,等待所有请求都成功,我们再去做什么事情jsconst delay = function (interval) { return new Promise((resolve, reject) => { //睡眠函数 setTimeout(() => { resolve(interval) }, interval) }) } let tasks = [() => { return delay(1000) }, () => { return delay(1003) }, () => { return delay(1005) }, () => { return delay(1002) }, () => { return delay(1006) }] //1.利用库函数asyncPool let results = [] asyncPool(2,tasks,(tasks,next)=>{ task().then(result =>{ results.push(result) next() }) },()=>{ console.log(results) }) //2. js实现 //tasks:数组,数组中包含很多方法,每一个方法相当于发一个请求 //pool:并发限制数量 function createRequest(tasks, pool) { pool = pool || 5 //工作区限制 let results = [], together = new Array(pool).fill(null), index = 0 together = together.map(() => { return new Promise((resolve, reject) => { const run = function () { if (index >= tasks.length) { resolve() return } let old_index = index, task = tasks[index++] //获取任务 task().then(result => { results[old_index] = result run() //递归获取任务 }).catch(reason => { reject(reason) }) } run() }) }) return Promise.all(together).then(() => results) //并发工作区内请求 } createRequest(tasks, 2).then(results => { console.log('====成功', results) //都成功才算成功,按顺序存储结果 }).catch((reason) => { console.log('====失败', reason) //只要有一个失败,整体失败 }) function createRequest2(tasks, pool, callback) { class TaskQueue { running = 0 queue = [] results = [] pushTack(task) { let self = this self.queue.push(task) self.next() } next() { let self = this while (self.running < pool && self.queue.length) { self.running++ let task = self.queue.shift() task().then(result => { self.results.push(result) }).finally(() => { self.running-- self.next() }) } if (self.running === 0) { callback(self.results) } } } } createRequest2(tasks, pool, results => { console.log(results) })