• 128. 什么是 rest 参数?

    rest 参数(形式为...变量名),用于获取函数的多余参数。

    129. 什么是尾调用,使用尾调用有什么好处?

    尾调用指的是函数的最后一步调用另一个函数。我们代码执行是基于执行栈的,所以当我们在一个函数里调用另一个函数时,我们会保留当前的执行上下文,然后再新建另外一个执行上下文加入栈中。使用尾调用的话,因为已经是函数的最后一步,所以这个时候我们可以不必再保留当前的执行上下文,从而节省了内存,这就是尾调用优化。但是 ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。

    130. Symbol 类型的注意点?

    131. Set 和 WeakSet 结构?

    132. Map 和 WeakMap 结构?

    133. 什么是 Proxy ?

    Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。

    Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

    134. Reflect 对象创建目的?

    135. require 模块引入的查找方式?

    当 Node 遇到 require(X) 时,按下面的顺序处理。

    (1)如果 X 是内置模块(比如 require('http'))
      a. 返回该模块。
      b. 不再继续执行。

    (2)如果 X 以 "./" 或者 "/" 或者 "../" 开头
      a. 根据 X 所在的父模块,确定 X 的绝对路径。
      b. 将 X 当成文件,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。
        X
        X.js
        X.json
        X.node

      c. 将 X 当成目录,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。
        X/package.json(main字段)
        X/index.js
        X/index.json
        X/index.node

    (3)如果 X 不带路径
      a. 根据 X 所在的父模块,确定 X 可能的安装目录。
      b. 依次在每个目录中,将 X 当成文件名或目录名加载。

    (4)抛出 "not found"

    详细资料可以参考:《require() 源码解读》

    136. 什么是 Promise 对象,什么是 Promises/A+ 规范?

    Promise 对象是异步编程的一种解决方案,最早由社区提出。Promises/A+ 规范是 JavaScript Promise 的标准,规定了一个 Promise 所必须具有的特性。

    Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是 pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者 rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。状态的改变是通过 resolve() 和 reject() 函数来实现的,我们
    可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。

    详细资料可以参考:《Promises/A+ 规范》《Promise》

    137. 手写一个 Promise

    const PENDING = "pending";
    const RESOLVED = "resolved";
    const REJECTED = "rejected";

    function MyPromise(fn{
      // 保存初始化状态
      var self = this;

      // 初始化状态
      this.state = PENDING;

      // 用于保存 resolve 或者 rejected 传入的值
      this.value = null;

      // 用于保存 resolve 的回调函数
      this.resolvedCallbacks = [];

      // 用于保存 reject 的回调函数
      this.rejectedCallbacks = [];

      // 状态转变为 resolved 方法
      function resolve(value{
        // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变
        if (value instanceof MyPromise) {
          return value.then(resolve, reject);
        }

        // 保证代码的执行顺序为本轮事件循环的末尾
        setTimeout(() => {
          // 只有状态为 pending 时才能转变,
          if (self.state === PENDING) {
            // 修改状态
            self.state = RESOLVED;

            // 设置传入的值
            self.value = value;

            // 执行回调函数
            self.resolvedCallbacks.forEach(callback => {
              callback(value);
            });
          }
        }, 0);
      }

      // 状态转变为 rejected 方法
      function reject(value{
        // 保证代码的执行顺序为本轮事件循环的末尾
        setTimeout(() => {
          // 只有状态为 pending 时才能转变
          if (self.state === PENDING) {
            // 修改状态
            self.state = REJECTED;

            // 设置传入的值
            self.value = value;

            // 执行回调函数
            self.rejectedCallbacks.forEach(callback => {
              callback(value);
            });
          }
        }, 0);
      }

      // 将两个方法传入函数执行
      try {
        fn(resolve, reject);
      } catch (e) {
        // 遇到错误时,捕获错误,执行 reject 函数
        reject(e);
      }
    }

    MyPromise.prototype.then = function(onResolved, onRejected{
      // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
      onResolved =
        typeof onResolved === "function"
          ? onResolved
          : function(value{
              return value;
            };

      onRejected =
        typeof onRejected === "function"
          ? onRejected
          : function(error{
              throw error;
            };

      // 如果是等待状态,则将函数加入对应列表中
      if (this.state === PENDING) {
        this.resolvedCallbacks.push(onResolved);
        this.rejectedCallbacks.push(onRejected);
      }

      // 如果状态已经凝固,则直接执行对应状态的函数

      if (this.state === RESOLVED) {
        onResolved(this.value);
      }

      if (this.state === REJECTED) {
        onRejected(this.value);
      }
    };

    138. 如何检测浏览器所支持的最小字体大小?

    用 JS 设置 DOM 的字体为某一个值,然后再取出来,如果值设置成功,就说明支持。

    139. 怎么做 JS 代码 Error 统计?

    error 统计使用浏览器的 window.error 事件。

    140. 单例模式模式是什么?

    单例模式保证了全局只有一个实例来被访问。比如说常用的如弹框组件的实现和全局状态的实现。

    141. 策略模式是什么?

    策略模式主要是用来将方法的实现和方法的调用分离开,外部通过不同的参数可以调用不同的策略。我主要在 MVP 模式解耦的时候
    用来将视图层的方法定义和方法调用分离。

    142. 代理模式是什么?

     代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。比如说常见的事件代理。

    143. 中介者模式是什么?

    中介者模式指的是,多个对象通过一个中介者进行交流,而不是直接进行交流,这样能够将通信的各个对象解耦。

    144. 适配器模式是什么?

    适配器用来解决两个接口不兼容的情况,不需要改变已有的接口,通过包装一层的方式实现两个接口的正常协作。假如我们需要一种
    新的接口返回方式,但是老的接口由于在太多地方已经使用了,不能随意更改,这个时候就可以使用适配器模式。比如我们需要一种
    自定义的时间返回格式,但是我们又不能对 js 时间格式化的接口进行修改,这个时候就可以使用适配器模式。

    更多关于设计模式的资料可以参考:《前端面试之道》《JavaScript 设计模式》《JavaScript 中常见设计模式整理》

    145. 观察者模式和发布订阅模式有什么不同?

    发布订阅模式其实属于广义上的观察者模式

    在观察者模式中,观察者需要直接订阅目标事件。在目标发出内容改变的事件后,直接接收事件并作出响应。

    而在发布订阅模式中,发布者和订阅者之间多了一个调度中心。调度中心一方面从发布者接收事件,另一方面向订阅者发布事件,订阅者需要在调度中心中订阅事件。通过调度中心实现了发布者和订阅者关系的解耦。使用发布订阅者模式更利于我们代码的可维护性。

    详细资料可以参考:《观察者模式和发布订阅模式有什么不同?》

    146. Vue 的生命周期是什么?

    Vue 的生命周期指的是组件从创建到销毁的一系列的过程,被称为 Vue 的生命周期。通过提供的 Vue 在生命周期各个阶段的钩子函数,我们可以很好的在 Vue 的各个生命阶段实现一些操作。

    147. Vue 的各个生命阶段是什么?

    Vue 一共有8个生命阶段,分别是创建前、创建后、加载前、加载后、更新前、更新后、销毁前和销毁后,每个阶段对应了一个生命周期的钩子函数。

    (1)beforeCreate 钩子函数,在实例初始化之后,在数据监听和事件配置之前触发。因此在这个事件中我们是获取不到 data 数据的。

    (2)created 钩子函数,在实例创建完成后触发,此时可以访问 data、methods 等属性。但这个时候组件还没有被挂载到页面中去,所以这个时候访问不到 $el 属性。一般我们可以在这个函数中进行一些页面初始化的工作,比如通过 ajax 请求数据来对页面进行初始化。

    (3)beforeMount 钩子函数,在组件被挂载到页面之前触发。在 beforeMount 之前,会找到对应的 template,并编译成 render 函数。

    (4)mounted 钩子函数,在组件挂载到页面之后触发。此时可以通过 DOM API 获取到页面中的 DOM 元素。

    (5)beforeUpdate 钩子函数,在响应式数据更新时触发,发生在虚拟 DOM 重新渲染和打补丁之前,这个时候我们可以对可能会被移除的元素做一些操作,比如移除事件监听器。

    (6)updated 钩子函数,虚拟 DOM 重新渲染和打补丁之后调用。

    (7)beforeDestroy 钩子函数,在实例销毁之前调用。一般在这一步我们可以销毁定时器、解绑全局事件等。

    (8)destroyed 钩子函数,在实例销毁之后调用,调用后,Vue 实例中的所有东西都会解除绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

    当我们使用 keep-alive 的时候,还有两个钩子函数,分别是 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数。

    详细资料可以参考:《vue 生命周期深入》《Vue 实例》

    148. Vue 组件间的参数传递方式?

    (1)父子组件间通信

    第一种方法是子组件通过 props 属性来接受父组件的数据,然后父组件在子组件上注册监听事件,子组件通过 emit 触发事
    件来向父组件发送数据。

    第二种是通过 ref 属性给子组件设置一个名字。父组件通过 $refs 组件名来获得子组件,子组件通过 $parent 获得父组
    件,这样也可以实现通信。

    第三种是使用 provider/inject,在父组件中通过 provider 提供变量,在子组件中通过 inject 来将变量注入到组件
    中。不论子组件有多深,只要调用了 inject 那么就可以注入 provider 中的数据。

    (2)兄弟组件间通信

    第一种是使用 eventBus 的方法,它的本质是通过创建一个空的 Vue 实例来作为消息传递的对象,通信的组件引入这个实
    例,通信的组件通过在这个实例上监听和触发事件,来实现消息的传递。

    第二种是通过 $parent.$refs 来获取到兄弟组件,也可以进行通信。

    (3)任意组件之间

    使用 eventBus ,其实就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。


    如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候采用上面这一些方法可能不利于项目的维护。这个时候
    可以使用 vuex ,vuex 的思想就是将这一些公共的数据抽离出来,将它作为一个全局的变量来管理,然后其他组件就可以对这个
    公共数据进行读写操作,这样达到了解耦的目的。

    详细资料可以参考:《VUE 组件之间数据传递全集》

    149. computed 和 watch 的差异?

    (1)computed 是计算一个新的属性,并将该属性挂载到 Vue 实例上,而 watch 是监听已经存在且已挂载到 Vue 实例上的数据,所以用 watch 同样可以监听 computed 计算属性的变化。

    (2)computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值。而 watch 则是当数据发生变化便会调用执行函数。

    (3)从使用场景上说,computed 适用一个数据被多个数据影响,而 watch 适用一个数据影响多个数据。

    详细资料可以参考:《做面试的不倒翁:浅谈 Vue 中 computed 实现原理》《深入理解 Vue 的 watch 实现原理及其实现方式》

    150. vue-router 中的导航钩子函数

    (1)全局的钩子函数 beforeEach 和 afterEach

    beforeEach 有三个参数,to 代表要进入的路由对象,from 代表离开的路由对象。next 是一个必须要执行的函数,如果不传参数,那就执行下一个钩子函数,如果传入 false,则终止跳转,如果传入一个路径,则导航到对应的路由,如果传入 error ,则导航终止,error 传入错误的监听函数。

    (2)单个路由独享的钩子函数 beforeEnter,它是在路由配置上直接进行定义的。

    (3)组件内的导航钩子主要有这三种:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。它们是直接在路由组
    件内部直接进行定义的。

    详细资料可以参考:《导航守卫》

    151. router 的区别?

    $route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。而 $router 是“路由实例”对象包括了路由的跳转方法,钩子函数等。

    152. vue 常用的修饰符?

    .prevent: 提交事件不再重载页面;.stop: 阻止单击事件冒泡;.self: 当事件发生在该元素本身而不是子元素的时候会触发;

    153. vue 中 key 值的作用?

    vue 中 key 值的作用可以分为两种情况来考虑。

    第一种情况是 v-if 中使用 key。由于 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。因此当我们使用 v-if 来实现元素切换的时候,如果切换前后含有相同类型的元素,那么这个元素就会被复用。如果是相同的 input 元素,那么切换前后用户的输入不会被清除掉,这样是不符合需求的。因此我们可以通过使用 key 来唯一的标识一个元素,这个情况下,使用 key 的元素不会被复用。这个时候 key 的作用是用来标识一个独立的元素。

    第二种情况是 v-for 中使用 key。用 v-for 更新已渲染过的元素列表时,它默认使用“就地复用”的策略。如果数据项的顺序发生了改变,Vue 不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处的每个元素。因此通过为每个列表项提供一个 key 值,来以便 Vue 跟踪元素的身份,从而高效的实现复用。这个时候 key 的作用是为了高效的更新渲染虚拟 DOM。

    详细资料可以参考:《Vue 面试中,经常会被问到的面试题 Vue 知识点整理》《Vue2.0 v-for 中 :key 到底有什么用?》《vue 中 key 的作用》

    154. computed 和 watch 区别?

    computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容。

    watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。

    155. keep-alive 组件有什么作用?

    如果你需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用 keep-alive 组件包裹需要保存的组件。

    156. vue 中 mixin 和 mixins 区别?

    mixin 用于全局混入,会影响到每个组件实例。

    mixins 应该是我们最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。另外需要注意的是 mixins 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并

    详细资料可以参考:《前端面试之道》《混入》

    157. 开发中常用的几种 Content-Type ?

    (1)application/x-www-form-urlencoded

    浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。该种方式提交的数据放在 body 里面,数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL
    转码。

    (2)multipart/form-data

    该种方式也是一个常见的 POST 提交方式,通常表单上传文件时使用该种方式。

    (3)application/json

    告诉服务器消息主体是序列化后的 JSON 字符串。

    (4)text/xml

    该种方式主要用来提交 XML 格式的数据。

    详细资料可以参考:《常用的几种 Content-Type》

    158. 如何封装一个 javascript 的类型判断函数?

    function getType(value{
      // 判断数据是 null 的情况
      if (value === null) {
        return value + "";
      }

      // 判断数据是引用类型的情况
      if (typeof value === "object") {
        let valueClass = Object.prototype.toString.call(value),
          type = valueClass.split(" ")[1].split("");

        type.pop();

        return type.join("").toLowerCase();
      } else {
        // 判断数据是基本数据类型的情况和函数的情况
        return typeof value;
      }
    }

    详细资料可以参考:《JavaScript 专题之类型判断(上)》

    159. 如何判断一个对象是否为空对象?

    function checkNullObj(obj{
      return Object.keys(obj).length === 0;
    }

    详细资料可以参考:《js 判断一个 object 对象是否为空》

    160. 使用闭包实现每隔一秒打印 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);
    }

    161. 手写一个 jsonp

    function jsonp(url, params, callback{
      // 判断是否含有参数
      let queryString = url.indexOf("?") === "-1" ? "?" : "&";

      // 添加参数
      for (var k in params) {
        if (params.hasOwnProperty(k)) {
          queryString += k + "=" + params[k] + "&";
        }
      }

      // 处理回调函数名
      let random = Math.random()
          .toString()
          .replace("."""),
        callbackName = "myJsonp" + random;

      // 添加回调函数
      queryString += "callback=" + callbackName;

      // 构建请求
      let scriptNode = document.createElement("script");
      scriptNode.src = url + queryString;

      window[callbackName] = function() {
        // 调用回调函数
        callback(...arguments);

        // 删除这个引入的脚本
        document.getElementsByTagName("head")[0].removeChild(scriptNode);
      };

      // 发起请求
      document.getElementsByTagName("head")[0].appendChild(scriptNode);
    }

    详细资料可以参考:《原生 jsonp 具体实现》《jsonp 的原理与实现》

    162. 手写一个观察者模式?

    var events = (function() {
      var topics = {};

      return {
        // 注册监听函数
        subscribefunction(topic, handler{
          if (!topics.hasOwnProperty(topic)) {
            topics[topic] = [];
          }
          topics[topic].push(handler);
        },

        // 发布事件,触发观察者回调事件
        publishfunction(topic, info{
          if (topics.hasOwnProperty(topic)) {
            topics[topic].forEach(function(handler{
              handler(info);
            });
          }
        },

        // 移除主题的一个观察者的回调事件
        removefunction(topic, handler{
          if (!topics.hasOwnProperty(topic)) return;

          var handlerIndex = -1;
          topics[topic].forEach(function(item, index{
            if (item === handler) {
              handlerIndex = index;
            }
          });

          if (handlerIndex >= 0) {
            topics[topic].splice(handlerIndex, 1);
          }
        },

        // 移除主题的所有观察者的回调事件
        removeAllfunction(topic{
          if (topics.hasOwnProperty(topic)) {
            topics[topic] = [];
          }
        }
      };
    })();

    详细资料可以参考:《JS 事件模型》

    163. EventEmitter 实现

    class EventEmitter {
      constructor() {
        this.events = {};
      }

      on(event, callback) {
        let callbacks = this.events[event] || [];
        callbacks.push(callback);
        this.events[event] = callbacks;

        return this;
      }

      off(event, callback) {
        let callbacks = this.events[event];
        this.events[event] = callbacks && callbacks.filter(fn => fn !== callback);

        return this;
      }

      emit(event, ...args) {
        let callbacks = this.events[event];
        callbacks.forEach(fn => {
          fn(...args);
        });

        return this;
      }

      once(event, callback) {
        let wrapFun = function(...args{
          callback(...args);

          this.off(event, wrapFun);
        };
        this.on(event, wrapFun);

        return this;
      }
    }

    164. 一道常被人轻视的前端 JS 面试题

    function Foo() {
      getName = function() {
        alert(1);
      };
      return this;
    }
    Foo.getName = function() {
      alert(2);
    };
    Foo.prototype.getName = function() {
      alert(3);
    };
    var getName = function() {
      alert(4);
    };
    function getName() {
      alert(5);
    }

    //请写出以下输出结果:
    Foo.getName(); // 2
    getName(); // 4
    Foo().getName(); // 1
    getName(); // 1
    new Foo.getName(); // 2
    new Foo().getName(); // 3
    new new Foo().getName(); // 3

    详细资料可以参考:《前端程序员经常忽视的一个 JavaScript 面试题》《一道考察运算符优先级的 JavaScript 面试题》《一道常被人轻视的前端 JS 面试题》

    165. 如何确定页面的可用性时间,什么是 Performance API?

    Performance API 用于精确度量、控制、增强浏览器的性能表现。这个 API 为测量网站性能,提供以前没有办法做到的精度。

    使用 getTime 来计算脚本耗时的缺点,首先,getTime方法(以及 Date 对象的其他方法)都只能精确到毫秒级别(一秒的千分之一),想要得到更小的时间差别就无能为力了。其次,这种写法只能获取代码运行过程中的时间进度,无法知道一些后台事件的时间进度,比如浏览器用了多少时间从服务器加载网页。

    为了解决这两个不足之处,ECMAScript 5引入“高精度时间戳”这个 API,部署在 performance 对象上。它的精度可以达到1毫秒
    的千分之一(1秒的百万分之一)。

    navigationStart:当前浏览器窗口的前一个网页关闭,发生 unload 事件时的 Unix 毫秒时间戳。如果没有前一个网页,则等于 fetchStart 属性。

    loadEventEnd:返回当前网页 load 事件的回调函数运行结束时的 Unix 毫秒时间戳。如果该事件还没有发生,返回 0。

    根据上面这些属性,可以计算出网页加载各个阶段的耗时。比如,网页加载整个过程的耗时的计算方法如下:

    var t = performance.timing;
    var pageLoadTime = t.loadEventEnd - t.navigationStart;

    详细资料可以参考:《Performance API》

    166. js 中的命名规则

    (1)第一个字符必须是字母、下划线(_)或美元符号($)
    (2)余下的字符可以是下划线、美元符号或任何字母或数字字符

    一般我们推荐使用驼峰法来对变量名进行命名,因为这样可以与 ECMAScript 内置的函数和对象命名格式保持一致。

    详细资料可以参考:《ECMAScript 变量》

    167. js 语句末尾分号是否可以省略?

    在 ECMAScript 规范中,语句结尾的分号并不是必需的。但是我们一般最好不要省略分号,因为加上分号一方面有
    利于我们代码的可维护性,另一方面也可以避免我们在对代码进行压缩时出现错误。

    168. Object.assign()

    Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

    169. Math.ceil 和 Math.floor

    Math.ceil() === 向上取整,函数返回一个大于或等于给定数字的最小整数。

    Math.floor() === 向下取整,函数返回一个小于或等于给定数字的最大整数。

    170. js for 循环注意点

    for (var i = 0, j = 0; i < 5, j < 9; i++, j++) {
      console.log(i, j);
    }

    // 当判断语句含有多个语句时,以最后一个判断语句的值为准,因此上面的代码会执行 10 次。
    // 当判断语句为空时,循环会一直进行。

    171. 一个列表,假设有 100000 个数据,这个该怎么办?

    我们需要思考的问题:该处理是否必须同步完成?数据是否必须按顺序完成?

    解决办法:

    (1)将数据分页,利用分页的原理,每次服务器端只返回一定数目的数据,浏览器每次只对一部分进行加载。

    (2)使用懒加载的方法,每次加载一部分数据,其余数据当需要使用时再去加载。

    (3)使用数组分块技术,基本思路是为要处理的项目创建一个队列,然后设置定时器每过一段时间取出一部分数据,然后再使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器。

    172. js 中倒计时的纠偏实现?

    在前端实现中我们一般通过 setTimeout 和 setInterval 方法来实现一个倒计时效果。但是使用这些方法会存在时间偏差的问题,这是由于 js 的程序执行机制造成的,setTimeout 和 setInterval 的作用是隔一段时间将回调事件加入到事件队列中,因此事件并不是立即执行的,它会等到当前执行栈为空的时候再取出事件执行,因此事件等待执行的时间就是造成误差的原因。

    一般解决倒计时中的误差的有这样两种办法:

    (1)第一种是通过前端定时向服务器发送请求获取最新的时间差,以此来校准倒计时时间。

    (2)第二种方法是前端根据偏差时间来自动调整间隔时间的方式来实现的。这一种方式首先是以 setTimeout 递归的方式来实现倒计时,然后通过一个变量来记录已经倒计时的秒数。每一次函数调用的时候,首先将变量加一,然后根据这个变量和每次的间隔时间,我们就可以计算出此时无偏差时应该显示的时间。然后将当前的真实时间与这个时间相减,这样我们就可以得到时间的偏差大小,因此我们在设置下一个定时器的间隔大小的时候,我们就从间隔时间中减去这个偏差大小,以此来实现由于程序执行所造成的时间误差的纠正。

    详细资料可以参考:《JavaScript 前端倒计时纠偏实现》

    173. 进程间通信的方式?

    详细资料可以参考:《进程间 8 种通信方式详解》《进程与线程的一个简单解释》

    174. 如何查找一篇英文文章中出现频率最高的单词?

    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;
    }


    
    
                   
                  
                 
                
               
              
             
            
           
    
    
          
    
    
         
    
    
        
    
    
       
    
    
      
    
    
    
    
    
      

    本文分享自微信公众号 - 前端开发社区(pt1173179243)。
    如有侵权,请联系 support@oschina.cn 删除。
    本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

    04-12 00:18