Appearance
三、场景应用
本节目标:理解“三、场景应用”的核心思路,并能把它用于实际开发或面试表达。
**1. 循环打印红黄绿**
下面来看一道比较典型的问题,通过这个问题来对比几种异步编程方法:**红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?**
三个亮灯函数:
==function red() {====== ==console.log('red');========}========function green() {====== ==console.log('green');========}========function yellow() {====== ==console.log('yellow');========}======
这道题复杂的地方在于**需要“交替重复”亮灯**,而不是“亮完一次”就结束了。
**(1)用 callback 实现**
==const task = (timer, light, callback) => {====== ==setTimeout(() => {====== ==if (light === 'red') {====== ==red()====== ==}====== ==else if (light === 'green') {====== ==green()====== ==}====== ==else if (light === 'yellow') {====== ==yellow()====== ==}====== ==callback()====== ==}, timer)========}========task(3000, 'red', () => {====== ==task(2000, 'green', () => {====== ==task(1000, 'yellow', Function.prototype)====== ==})========})======
这里存在一个 bug:代码只是完成了一次流程,执行后红黄绿灯分别只亮一次。该如何让它交替重复进行呢?
上面提到过递归,可以递归亮灯的一个周期:
==const step = () => {====== ==task(3000, 'red', () => {====== ==task(2000, 'green', () => {====== ==task(1000, 'yellow', step)====== ==})====== ==})========}========step()======
**注意看黄灯亮的回调里又再次调用了 step 方法** 以完成循环亮灯。
**(2)用 promise 实现**
==const task = (timer, light) =>== ==== ==new Promise((resolve, reject) => {====== ==setTimeout(() => {====== ==if (light === 'red') {====== ==red()====== ==}====== ==else if (light === 'green') {====== ==green()====== ==}====== ==else if (light === 'yellow') {====== ==yellow()====== ==}====== ==resolve()====== ==}, timer)====== ==})========const step = () => {====== ==task(3000, 'red')====== ==.then(() => task(2000, 'green'))====== ==.then(() => task(2100, 'yellow'))====== ==.then(step)========}========step()======
这里将回调移除,在一次亮灯结束后,resolve 当前 promise,并依然使用递归进行。
**(3)用 async/await 实现**
==const taskRunner = async () => {====== ==await task(3000, 'red')====== ==await task(2000, 'green')====== ==await task(2100, 'yellow')====== ==taskRunner()========}========taskRunner()======
**2. 实现每隔一秒打印 1,2,3,4**
==//== ==使用闭包实现========for (var i = 0; i < 5; i++) {====== ==(function(i) {====== ==setTimeout(function() {====== ==console.log(i);====== ==}, i * 1000);====== ==})(i);========}========//== ==使用== ==let== ==块级作用域========for (let i = 0; i < 5; i++) {====== ==setTimeout(function() {====== ==console.log(i);====== ==}, i * 1000);========}======
**3. 小孩报数问题**
有30个小孩儿,编号从1-30,围成一圈依此报数,1、2、3 数到 3 的小孩儿退出这个圈, 然后下一个小孩 重新报数 1、2、3,问最后剩下的那个小孩儿的编号是多少?
==function childNum(num, count){====== ==let allplayer = [];== ==== ==for(let i = 0; i < num; i++){====== ==allplayer[i] = i + 1;====== ==}====== ==== ==let exitCount = 0; //== ==离开人数====== ==let counter = 0; //== ==记录报数====== ==let curIndex = 0; //== ==当前下标====== ==== ==while(exitCount < num - 1){====== ==if(allplayer[curIndex] !== 0) counter++;== ==== ==== ==if(counter == count){====== ==allplayer[curIndex] = 0;== ==== ==counter = 0;====== ==exitCount++;== ==== ==}====== ==curIndex++;====== ==if(curIndex == num){====== ==curIndex = 0== ==== ==};== ==== ==}== ==== ==for(i = 0; i < num; i++){====== ==if(allplayer[i] !== 0){====== ==return allplayer[i]====== ==}== ==== ==}========}========childNum(30, 3)======
**4. 用Promise实现图片的异步加载**
==let imageAsync=(url)=>{====== ==return new Promise((resolve,reject)=>{====== ==let img = new Image();====== ==img.src = url;====== ==img.οnlοad=()=>{====== ==console.log(`====图片请求成功,此处进行通用操作====`);====== ==resolve(image);====== ==}====== ==img.οnerrοr=(err)=>{====== ==console.log(`====失败,此处进行失败的通用操作====`);====== ==reject(err);====== ==}====== ==})====== ==}====== ======imageAsync("url").then(()=>{====== ==console.log("====加载成功====");========}).catch((error)=>{====== ==console.log("====加载失败====");========})======
**5. 实现发布-订阅模式**
==class EventCenter{====== ==// 1.== ==定义事件容器,用来装事件数组====== ==let handlers = {}======
==// 2.== ==添加事件方法,参数:事件名== ==事件方法====== ==addEventListener(type, handler) {====== ==//== ==创建新数组容器====== ==if (!this.handlers[type]) {====== ==this.handlers[type] = []====== ==}====== ==//== ==存入事件====== ==this.handlers[type].push(handler)====== ==}======
==// 3.== ==触发事件,参数:事件名== ==事件参数====== ==dispatchEvent(type, params) {====== ==//== ==若没有注册该事件则抛出错误====== ==if (!this.handlers[type]) {====== ==return new Error('====该事件未注册====')====== ==}====== ==//== ==触发事件====== ==this.handlers[type].forEach(handler => {====== ==handler(...params)====== ==})====== ==}======
==// 4.== ==事件移除,参数:事件名== ==要删除事件,若无第二个参数则删除该事件的订阅和发布====== ==removeEventListener(type, handler) {====== ==if (!this.handlers[type]) {====== ==return new Error('====事件无效====')====== ==}====== ==if (!handler) {====== ==//== ==移除事件====== ==delete this.handlers[type]====== ==} else {====== ==const index = this.handlers[type].findIndex(el => el === handler)====== ==if (index === -1) {====== ==return new Error('====无该绑定事件====')====== ==}====== ==//== ==移除事件====== ==this.handlers[type].splice(index, 1)====== ==if (this.handlers[type].length === 0) {====== ==delete this.handlers[type]====== ==}====== ==}====== ==}========}======
==折叠==
**6. 查找文章中出现频率最高的单词**
==function findMostWord(article) {====== ==//== ==合法性判断====== ==if (!article) return;====== ==//== ==参数处理====== ==article = article.trim().toLowerCase();====== ==let wordList = article.match(/[a-z]+/g),====== ==visited = [],====== ==maxNum = 0,====== ==maxWord = "";====== ==article = " " + wordList.join(" ") + " ";====== ==//== ==遍历判断单词出现次数====== ==wordList.forEach(function(item) {====== ==if (visited.indexOf(item) < 0) {====== ==//== ==加入== ==visited== ==== ==visited.push(item);====== ==let word = new RegExp(" " + item + " ", "g"),====== ==num = article.match(word).length;====== ==if (num > maxNum) {====== ==maxNum = num;====== ==maxWord = item;====== ==}====== ==}====== ==});====== ==return maxWord + " " + maxNum;========}======
**7. 封装异步的fetch,使用async await方式来使用**
==(async () => {====== ==class HttpRequestUtil {====== ==async get(url) {====== ==const res = await fetch(url);====== ==const data = await res.json();====== ==return data;====== ==}====== ==async post(url, data) {====== ==const res = await fetch(url, {====== ==method: 'POST',====== ==headers: {====== =='Content-Type': 'application/json'====== ==},====== ==body: JSON.stringify(data)====== ==});====== ==const result = await res.json();====== ==return result;====== ==}====== ==async put(url, data) {====== ==const res = await fetch(url, {====== ==method: 'PUT',====== ==headers: {====== =='Content-Type': 'application/json'====== ==},====== ==data: JSON.stringify(data)====== ==});====== ==const result = await res.json();====== ==return result;====== ==}====== ==async delete(url, data) {====== ==const res = await fetch(url, {====== ==method: 'DELETE',====== ==headers: {====== =='Content-Type': 'application/json'====== ==},====== ==data: JSON.stringify(data)====== ==});====== ==const result = await res.json();====== ==return result;====== ==}====== ==}====== ==const httpRequestUtil = new HttpRequestUtil();====== ==const res = await httpRequestUtil.get('http://golderbrother.cn/');====== ==console.log(res);========})();======
**8. 实现prototype继承**
所谓的原型链继承就是让新实例的原型等于父类的实例:
==//====父方法========function SupperFunction(flag1){====== ==this.flag1 = flag1;========}======
==//====子方法========function SubFunction(flag2){====== ==this.flag2 = flag2;========}======
==//====父实例========var superInstance = new SupperFunction(true);======
==//====子继承父========SubFunction.prototype = superInstance;======
==//====子实例========var subInstance = new SubFunction(false);========//====子调用自己和父的属性========subInstance.flag1; // true========subInstance.flag2; // false======
**9. 实现双向数据绑定**
==let obj = {}========let input = document.getElementById('input')========let span = document.getElementById('span')========//== ==数据劫持========Object.defineProperty(obj, 'text', {====== ==configurable: true,====== ==enumerable: true,====== ==get() {====== ==console.log('====获取数据了====')====== ==},====== ==set(newVal) {====== ==console.log('====数据更新了====')====== ==input.value = newVal====== ==span.innerHTML = newVal====== ==}========})========//== ==输入监听========input.addEventListener('keyup', function(e) {====== ==obj.text = e.target.value========})======
**10. 实现简单路由**
==// hash====路由========class Route{====== ==constructor(){====== ==//== ==路由存储对象====== ==this.routes = {}====== ==//== ==当前====hash====== ==this.currentHash = ''====== ==//== ==绑定====this====,避免监听时====this====指向改变====== ==this.freshRoute = this.freshRoute.bind(this)====== ==//== ==监听====== ==window.addEventListener('load', this.freshRoute, false)====== ==window.addEventListener('hashchange', this.freshRoute, false)====== ==}====== ==//== ==存储====== ==storeRoute (path, cb) {====== ==this.routes[path] = cb || function () {}====== ==}====== ==//== ==更新====== ==freshRoute () {====== ==this.currentHash = location.hash.slice(1) || '/'====== ==this.routes[this.currentHash]()====== ==}========}======
**11. 实现斐波那契数列**
==//== ==递归========function fn (n){====== ==if(n==0) return 0====== ==if(n==1) return 1====== ==return fn(n-2)+fn(n-1)========}========//== ==优化========function fibonacci2(n) {====== ==const arr = [1, 1, 2];====== ==const arrLen = arr.length;======
==if (n <= arrLen) {====== ==return arr[n];====== ==}======
==for (let i = arrLen; i < n; i++) {====== ==arr.push(arr[i - 1] + arr[ i - 2]);====== ==}======
==return arr[arr.length - 1];========}========//== ==非递归========function fn(n) {====== ==let pre1 = 1;====== ==let pre2 = 1;====== ==let current = 2;======
==if (n <= 2) {====== ==return current;====== ==}======
==for (let i = 2; i < n; i++) {====== ==pre1 = pre2;====== ==pre2 = current;====== ==current = pre1 + pre2;====== ==}======
==return current;========}======
**12. 字符串出现的不重复最长长度**
用一个滑动窗口装没有重复的字符,枚举字符记录最大值即可。用 map 维护字符的索引,遇到相同的字符,把左边界移动过去即可。挪动的过程中记录最大长度:
==var lengthOfLongestSubstring = function (s) {====== ==let map = new Map();====== ==let i = -1====== ==let res = 0====== ==let n = s.length====== ==for (let j = 0; j < n; j++) {====== ==if (map.has(s[j])) {====== ==i = Math.max(i, map.get(s[j]))====== ==}====== ==res = Math.max(res, j - i)====== ==map.set(s[j], j)====== ==}====== ==return res========};======
**13. 使用 setTimeout 实现 setInterval**
setInterval 的作用是每隔一段指定时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。
针对 setInterval 的这个缺点,我们可以使用 setTimeout 递归调用来模拟 setInterval,这样我们就确保了只有一个事件结束了,我们才会触发下一个定时器事件,这样解决了 setInterval 的问题。
实现思路是使用递归函数,不断地去执行 setTimeout 从而达到 setInterval 的效果
==function mySetInterval(fn, timeout) {====== ==//== ==控制器,控制定时器是否继续执行====== ==var timer = {====== ==flag: true====== ==};====== ==//== ==设置递归函数,模拟定时器执行。====== ==function interval() {====== ==if (timer.flag) {====== ==fn();====== ==setTimeout(interval, timeout);====== ==}====== ==}====== ==//== ==启动定时器====== ==setTimeout(interval, timeout);====== ==//== ==返回控制器====== ==return timer;========}======
**14. 实现 jsonp**
==//== ==动态的加载====js====文件========function addScript(src) {====== ==const script = document.createElement('script');====== ==script.src = src;====== ==script.type = "text/javascript";====== ==document.body.appendChild(script);========}========addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");========//== ==设置一个全局的====callback====函数来接收回调结果========function handleRes(res) {====== ==console.log(res);========}========//== ==接口返回的数据格式========handleRes({a: 1, b: 2});======
**15. 判断对象是否存在循环引用**
循环引用对象本来没有什么问题,但是序列化的时候就会发生问题,比如调用==JSON.stringify()==对该类对象进行序列化,就会报错: ==Converting circular structure to JSON.==
下面方法可以用来判断一个对象中是否已存在循环引用:
==const isCycleObject = (obj,parent) => {====== ==const parentArr = parent || [obj];====== ==for(let i in obj) {====== ==if(typeof obj[i] === 'object') {====== ==let flag = false;====== ==parentArr.forEach((pObj) => {====== ==if(pObj === obj[i]){====== ==flag = true;====== ==}====== ==})====== ==if(flag) return true;====== ==flag = isCycleObject(obj[i],[...parentArr,obj[i]]);====== ==if(flag) return true;====== ==}====== ==}====== ==return false;========}======
==const a = 1;========const b = {a};========const c = {b};========const o = {d:{a:3},c}========o.c.b.aa = a;======
==console.log(isCycleObject(o)======
查找有序二维数组的目标值:
==var findNumberIn2DArray = function(matrix, target) {====== ==if (matrix == null || matrix.length == 0) {====== ==return false;====== ==}====== ==let row = 0;====== ==let column = matrix[0].length - 1;====== ==while (row < matrix.length && column >= 0) {====== ==if (matrix[row][column] == target) {====== ==return true;====== ==} else if (matrix[row][column] > target) {====== ==column--;====== ==} else {====== ==row++;====== ==}====== ==}====== ==return false;========};======二维数组斜向打印:
==function printMatrix(arr){====== ==let m = arr.length, n = arr[0].length====== ==let res = []====== ==== ==//== ==左上角,从====0== ==到== ==n - 1== ==列进行打印====== ==for (let k = 0; k < n; k++) {====== ==for (let i = 0, j = k; i < m && j >= 0; i++, j--) {====== ==res.push(arr[i][j]);====== ==}====== ==}======
==//== ==右下角,从====1== ==到== ==n - 1== ==行进行打印====== ==for (let k = 1; k < m; k++) {====== ==for (let i = k, j = n - 1; i < m && j >= 0; i++, j--) {====== ==res.push(arr[i][j]);====== ==}====== ==}====== ==return res========}==