2025年3月15日 星期六 甲辰(龙)年 月十四 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > JavaScript

从零开始:手写 JavaScript 代码应用于实际场景

时间:02-18来源:作者:点击数:15
城东书院 www.cdsy.xyz

场景应用

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;
  • }
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
  • }
城东书院 www.cdsy.xyz
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐