Skip to content

三、场景应用 ​

本节目标:理解“三、场景应用”的核心思路,并能把它用于实际开发或面试表达。

**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========}==

> 来自 <https://www.cnblogs.com/mooz88/p/16022281.html>