简述同步和异步的区别
众所周知,javascript是单线程的语言,所谓的单线程,就是从上至下,依次执行,当然这里的依次执行要抛开javascript的预解析机制。
这样做的原因是因为javascript最初是为了操作DOM,运行在浏览器环境下的,而操作DOM的时候,不能是异步的,不然的话两个异步任务同时修改DOM结构的话,会导致浏览器不知道该执行哪一个。
但是这样做也有缺点,当遇到一个响应时间特别长的任务时,容易导致页面加载错误或者浏览器未响应的情况。
同步就是所有的任务都处在同一队列中,不可以插队,一个任务执行完接着开始执行下一个,相对于浏览器而言,同步的效率过低,一些耗费时间比较长的任务应该用异步来执行。
异步就是将一个任务放入到异步队列中,当这个任务执行完成之后,再从异步队列中提取出来,插队到同步队列中,拿到异步任务的结果,可以提升代码执行的效率,不需要因为一个耗费时长的代码而一直等待。
javascript异步的几种实现方式
-
回调函数 callback
最简单的回调函数的实现方式,利用setTimeout会进入计时器队列,而不是正常队列的特性,来实现回调函数
计时器队列与正常队列同时运行,不影响正常队列的运行,在正常队列空闲的时候,并且计时器队列的任务倒计时已经结束,就将计时器队列的任务提取到正常队列中获得最终结果。-
callback示例返回值function syncMethod(callback) { setTimeout(function() { callback(count); console.log('第一次调用回调函数!'); setTimeout(function() { callback(count); console.log('第二次调用回调函数!'); }) for(let i=0;i<100;i++) { count++; } }, 0); let count = 1; for(let i=0;i<100;i++) { count++; } } syncMethod(function(count) { console.log(count); // 调用两次,第一次返回101,第二次返回201 })
-
-
事件驱动
- javascript是基于事件驱动的语言,当我们给元素绑定事件的时候,并不会立即执行,而是当触发指定的事件时,才会执行对应的方法
const btn = document.querySelector('button'); btn.addEventlistener('click', function() { console.log('clickBtn') })
-
发布者订阅者模式(观察者模式)
- 发布者订阅者模式是通过保存事件,然后在需要使用的时候直接发布事件,就可以触发保存的回调
-
Image 3.jpgfunction Watcher() { this.events = {}; // 定义事件对象,用来保存需要触发的事件 }; // $on方法,用来监听事件,订阅者 Watcher.prototype.$on = function(event, handler) { this.events[event] = this.events[event] || []; // 如果当前事件已经被监听,则在原基础上进行添加新的事件,如果没有的话新建一个数组,用来存储事件 this.events[event].push({ handler: handler }) // 将事件存储到对应的事件模型中 }; // $emit方法,用来发布事件,发布者 Watcher.prototype.$emit = function(event,...arg) { // 如果没有订阅当前事件的话,直接返回 if(!this.events[event]) { return } // 循环遍历对应事件,将对应的事件根据填入的先后顺序触发 for(let i = 0; i< this.events[event].length; i++) { this.events[event][i].handler(...arg) } }; // 关闭监听事件 Watcher.prototype.$close = function(event) { // 如果没有订阅当前事件的话,直接返回 if(!this.events[event]) { return } // 删除订阅事件 delete this.events[event]; }; // 实例化一个观察者 let watcher= new Watcher(); // 监听事件,并传入事件需要触发的函数 watcher.$on('handler1', function(name, age) { console.log(name, age, '我是第一个函数') }); // 监听不同的事件 watcher.$on('handler2', function(sex) { console.log(sex) }); // 监听同一个事件,触发不同的函数 watcher.$on('handler1', function(name, age) { console.log(name, age, '我是第二个函数') }); // 发布事件,并传参 watcher.$emit('handler2', "男"); watcher.$emit('handler1', '张三',20); // 关闭事件 watcher.$close('handler1'); // 事件不会触发,因为已经关闭了对应事件的监听 watcher.$emit('handler1', '张三',20);
-
promise 异步编程的解决方案
- Promise是ES6新增的异步编程的解决方案,它有三种状态分别是
pending
进行中、resolved
已完成、rejected
已失败。 - Promise可以链式调用,避免层层嵌套,异步操作更加容易方便,提升代码可读性。
- Promise一旦创建就不可以取消,一旦在
resolved
与rejected
中确立一种状态,就不可以再发生改变 function asynMethod(flag) { return new Promise((resolve, reject) => { setTimeout(() => { if(flag) { resolve('郝晨光精心写作,如有错误请指出,谢谢!'); }else { reject('错误信息!') } }, 3000) }) } asynMethod(true).then(data => { console.log(data); return asynMethod(true) }).then(data => { console.log(data); return asynMethod(false) }).catch(err =>{ console.log(err); })
- Promise是ES6新增的异步编程的解决方案,它有三种状态分别是
数组去重(手写代码)
- 使用ES6的Set去重
- Set是ES6新增的数据类型,Set 的成员具有唯一性
function distinct(arr) { return Array.from(new Set(arr)); }
- 使用ES6的Set去重(超级简化版)
[...new Set(arr)] // [...new Set(需要去重的数组)]
- 使用splice配合两重for循环去重
function distinct(arr) { for(let i = 0; i < arr.length; i++) { for(let j = i + 1; j < arr.length; j++) { if(arr[i] === arr[j]) { arr.splice(j, 1); j--; } } } return arr; }
- 使用for循环配合indexOf去重
function distinct(arr) { let newArr = []; for(let i = 0; i < arr.length; i++) { if(newArr.indexOf(arr[i]) === -1) { newArr.push(arr[i]); } } return newArr; }
- 使用for循环配合sort排序去重
function distinct(arr) { arr = arr.sort(); let newArr = []; for(let i = 0; i < arr.length; i++) { if(arr[i] !== arr[i-1]) { newArr.push(arr[i]); } } return newArr; }
- 使用for循环配合includes去重
function distinct(arr) { let newArr = []; for(let i = 0; i < arr.length; i++) { if(!newArr.includes(arr[i])) { newArr.push(arr[i]); } } return newArr; }
- 使用filter配合indexOf去重
function distinct(arr) { return arr.filter((item,index, arr) => arr.indexOf(item) === index); }
在JavaScript中什么是伪数组?如何将伪数组转化为标准数组
javascript中的伪数组(类数组):不具有数组的push,pop等方法,但是具有length,以及可以利用for循环遍历等特性,例如函数的 arguments 参数集合,还有通过document.getElementsByTagName
等方法获取的NodeList等都是类数组
如何将伪数组转化为标准数组
let nodeList = document.getElementsByTagName('li'); let nodeArr = Array.from(nodeList);
let nodeList = document.getElementsByTagName('li'); let nodeArr = Array.prototype.slice.call(nodeList);
当转化为标准数组以后,就可以调用数组实例上的方法了,如push、pop、splice、filter、map、forEach等等
SPA路由history模式,打包上线都遇到了哪些问题?你是如何解决的?
- 资源路径404问题
- vue-cli3环境下,在根目录新建vue.config.js,在该文件中写入如下
module.exports = { publicPath: './' }
- 页面刷新404问题
- 服务器端配置,详见 前端常见面试题(六)@郝晨光 中 SPA 路由history模式上线后刷新404
JavaScript中callee和caller的作用
-
callee是函数arguments对象内的指针,它指向当前的函数,使得在函数内部递归调用当前函数时,不需要调用函数名称,减少函数内部对于函数名的依赖
function test() { console.log(arguments.callee); } test();
-
caller是函数的一个属性,它指向调用当前函数的函数,如果当前函数在其他函数内被调用,则返回调用它的那个函数,如果是在全局环境下被调用,则返回 null
我们可以利用caller的特性跟踪函数的调用链function fn1() { console.log(fn1.caller); console.log(arguments.callee.caller); } function fn2() { fn1() } fn2();
网友评论