简述同步和异步的区别
众所周知, JavaScript 是单线程的语言, 所谓的单线程, 就是从上至下, 依次执行, 当然这里的依次执行要抛开 JavaScript 的预解析机制.
这样做的原因是因为 JavaScript 最初是为了操作 DOM, 运行在浏览器环境下的, 而操作 DOM 的时候, 不能是异步的, 不然的话两个异步任务同时修改 DOM 结构的话, 会导致浏览器不知道该执行哪一个.
但是这样做也有缺点, 当遇到一个响应时间特别长的任务时, 容易导致页面加载错误或者浏览器未响应的情况.
同步就是所有的任务都处在同一队列中, 不可以插队, 一个任务执行完接着开始执行下一个, 相对于浏览器而言, 同步的效率过低, 一些耗费时间比较长的任务应该用异步来执行.
异步就是将一个任务放入到异步队列中, 当这个任务执行完成之后, 再从异步队列中提取出来, 插队到同步队列中, 拿到异步任务的结果, 可以提升代码执行的效率, 不需要因为一个耗费时长的代码而一直等待.
JavaScript 异步的几种实现方式
回调函数 callback
最简单的回调函数的实现方式, 利用 setTimeout 会进入计时器队列, 而不是正常队列的特性, 来实现回调函数
计时器队列与正常队列同时运行, 不影响正常队列的运行, 在正常队列空闲的时候, 并且计时器队列的任务倒计时已经结束, 就将计时器队列的任务提取到正常队列中获得最终结果.
- 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
- })
callback 示例返回值
事件驱动
JavaScript 是基于事件驱动的语言, 当我们给元素绑定事件的时候, 并不会立即执行, 而是当触发指定的事件时, 才会执行对应的方法
- const btn = document.querySelector('button');
- btn.addEventlistener('click', function() {
- console.log('clickBtn')
- })
发布者订阅者模式 (观察者模式)
发布者订阅者模式是通过保存事件, 然后在需要使用的时候直接发布事件, 就可以触发保存的回调
- function 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);
Image 3.jpg
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);
- })
数组去重 (手写代码)
使用 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();
结言
感谢您的查阅, 代码冗余或者有错误的地方望不吝赐教; 菜鸟一枚, 请多关照
来源: http://www.jianshu.com/p/8400cbd0f8e2