# 2019年04月刊

# 2019/04/22 - 2019/04/28 ⌚️


  • new 一个对象经历了什么

    点击
    function Test() {}
    const test = new Test();
    
    1
    2
    1. 创建一个对象 const obj = {}

    2. 设置新对象的 constructor 属性为构造函数的名称,设置新对象的 __proto__ 属性指向构造函数的 prototype 对象

    obj.__proto__ = Test.prototype;
    obj.__proto__.constructor = Test;
    
    1
    2
    1. 使用新对象调用函数,函数中的 this 被指向新实例对象 Test.call(obj)

    2. 将初始化完毕的新对象地址,保存到等号左边的变量中

  • event.targetevent.currentTarget 区别

event.target 返回触发事件的元素, event.currentTarget 返回绑定事件的元素

  • Vue <transition> 的类名详解?

    点击

    在进入/离开的过渡中,会有 6 个 class 切换。

    v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。

    v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。

    v-enter-to: 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。

    v-leave: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。

    v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。

    v-leave-to: 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。

  • 监听页面关闭或者刷新事件?

    点击

    页面加载时只执行 onload 事件。

    页面关闭时,先 onbeforeunload 事件,再 onunload 事件。

    页面刷新时先执行 onbeforeunload 事件,然后 onunload 事件,最后 onload 事件。

    因此监听 onbeforeunload 事件,如下:

    window.addEventListener('beforeunload', e => this.beforeunloadFn(e));
    window.removeEventListener('beforeunload', e => this.beforeunloadFn(e));
    
    1
    2
  • 如何实现点击按钮下载文件?

    点击 这个时候就需要给 a 标签添加一个属性“download”,如:
    <a
      href="https://github.com/zxpsuper/Demo/archive/master.zip"
      download="master.zip"
      >点击下载</a
    >
    
    1
    2
    3
    4
    5
  • 立即执行函数 ?

    点击

    立即执行函数(IIFE)常用于第三方库,好处在于隔离作用域,任何一个第三方库都会存在大量的变量和函数,为了避免变量污染(命名冲突),开发者们想到的解决办法就是使用立即执行函数。

    通过定义一个匿名函数,创建了一个新的函数作用域,相当于创建了一个“私有”的命名空间,该命名空间的变量和方法,不会破坏污染全局的命名空间。此时若是想访问全局对象,将全局对象以参数形式传进去即可

    (function() {})();
    
    1
  • 函数柯里化的理解?

    点击

    在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

    那究竟柯里化有什么作用呢?常见的作用是:参数复用、延迟运行、扁平化

    函数柯里化(curry)的定义很简单:传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

    比如对于加法函数 var add = (x, y) => x + y ,我们可以这样进行柯里化:

    //比较容易读懂的ES5写法
    var add = function(x) {
      return function(y) {
        return x + y;
      };
    };
    
    //ES6写法,也是比较正统的函数式写法
    var add = x => y => x + y;
    
    //试试看
    var add2 = add(2);
    var add200 = add(200);
    
    add2(2); // =>4
    add200(50); // =>250
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

# 2019/04/15 - 2019/04/21 ⌚️


  • 写一个乱序函数 ?

    点击

    遍历数组元素,然后将当前元素与以后随机位置的元素进行交换。

    function shuffle(a) {
      for (let i = a.length; i; i--) {
        let j = Math.floor(Math.random() * i);
        // es6语法
        [a[i - 1], a[j]] = [a[j], a[i - 1]];
      }
      return a;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
  • 什么是惰性函数?

    点击

    惰性函数就是返回一个重写函数。example:

    var foo = function() {
      var t = new Date();
      foo = function() {
        return t;
      };
      return foo();
    };
    
    foo();
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • 静态作用域与动态作用域 ?

    点击

    静态作用域 —— 函数的作用域基于函数创建的位置。

    动态作用域 —— 函数的作用域基于函数的使用位置。

    var value = 1;
    
    function foo() {
      console.log(value);
    }
    
    function bar() {
      var value = 2;
      foo();
    }
    
    bar(); // 输出 1 。JavaScript 采用的是词法作用域,也称为静态作用域。相同的,动态作用域此代码应该输出 2
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  • 手写一个 function call()函数 ?

    点击
    Function.prototype.call2 = function(context, ...args) {
      // 因为传进来的 context 有可能是 null
      context = context || window;
      // Function.prototype this 为当前运行的函数
      // 让 fn 的上下文为 context
      context.fn = this;
    
      const result = context.fn(...args);
    
      delete context.fn;
    
      return result;
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  • Vue 组件中的 name 属性的作用 ?

    点击

    组件在全局用 Vue.component() 注册时,全局 ID 自动作为组件的 name。

    指定 name 选项的另一个好处是便于调试。有名字的组件有更友好的警告信息。另外,当在有 vue-devtools,未命名组件将显示成 <AnonymousComponent>,这很没有语义。通过提供 name 选项,可以获得更有语义信息的组件树。

  • Hash 路由和 History 路由的区别 ?

    点击
    1. hash 路由

    hash 路由一个明显的标志是带有#,我们主要是通过监听 url 中的 hash 变化来进行路由跳转。(window.addEventListener('hashchange', this.refresh, false);)

    hash 的优势就是兼容性更好,在老版 IE 中都有运行,问题在于 url 中一直存在#不够美观,而且 hash 路由更像是 Hack 而非标准,相信随着发展更加标准化的 History API 会逐步蚕食掉 hash 路由的市场。

    1. history 路由

    history 路由使用 History API 来实现,具体有:

    window.history.back(); // 后退
    window.history.forward(); // 前进
    window.history.go(-3); // 后退三个页面
    
    1
    2
    3

    history.pushState用于在浏览历史中添加历史记录, history.replaceState方法的参数与pushState方法一模一样,区别是它修改浏览历史中当前纪录,而非添加记录,同样不触发跳转。

  • Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty

    点击
    1. Object.defineProperty 无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应;

    2. Object.defineProperty 只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。Proxy 可以劫持整个对象,并返回一个新的对象。

    3. Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

# 2019/04/08 - 2019/04/14 ⌚️


  • 宏任务和微任务分别有哪些

    点击

    宏任务: setTimeout,setInterval,setImmediate (Node独有),requestAnimationFrame (浏览器独有),I/O,UI rendering (浏览器独有)

    微任务: process.nextTick (Node 独有),Promise,Object.observe,MutationObserver

  • 写一个“终极类型”判断函数?

    点击
    function type(obj) {
      var toString = Object.prototype.toString;
      var toType = {};
      var typeArr = [
        'Undefined',
        'Null',
        'Boolean',
        'Number',
        'String',
        'Object',
        'Array',
        'Function',
        'Date',
        'RegExp',
        'Error',
        'Arguments',
      ];
      typeArr.map(function(item, index) {
        toType['[object ' + item + ']'] = item.toLowerCase();
      });
    
      return typeof obj !== 'object' ? typeof obj : toType[toString.call(obj)];
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
  • 写一个函数,判断各种类型的不同变量是否相等,即“终极等于”函数?

    点击
    const equals = (a, b) => {
      if (a === b) return true;
      if (a instanceof Date && b instanceof Date)
        return a.getTime() === b.getTime();
      if (!a || !b || (typeof a !== 'object' && typeof b !== 'object'))
        return a === b;
      if (a.prototype !== b.prototype) return false;
      if (Array.isArray(a) && Array.isArray(b)) a.sort(), b.sort();
    
      let keys = Object.keys(a);
      if (keys.length !== Object.keys(b).length) return false;
      return keys.every(k => equals(a[k], b[k]));
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  • mouseover 和 mouseenter 的区别 ?

    点击 mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。对应的移除事件是 mouseout

    mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,对应的移除事件是 mouseleave

  • 一句话形容闭包?

    闭包就是能够读取其他函数内部变量的函数,或者子函数在外调用,子函数所在的父函数的作用域不会被释放。

    因为闭包引用着另一个函数的变量,导致另一个函数已经不使用了也无法销毁,所以闭包使用过多,会占用较多的内存,这也是一个副作用。

  • js 的 new 操作符做了哪些事情 ?

    new 操作符新建了一个空对象,这个对象原型指向构造函数的 prototype,执行构造函数后返回这个对象。

  • 实现一个深拷贝 ?

    点击
    //所谓深度克隆,就是当对象的某个属性值为object或array的时候,要获得一份copy,而不是直接拿到引用值
    
    function deepClone1(origin, target) {
      //origin是被克隆对象,target是我们获得copy
      var target = target || {}; //定义target
      for (var key in origin) {
        //遍历原对象
        if (origin.hasOwnProperty(key)) {
          if (Array.isArray(origin[key])) {
            //如果是数组
            target[key] = [];
            deepClone1(origin[key], target[key]); //递归
          } else if (typeof origin[key] === 'object' && origin[key] !== null) {
            target[key] = {};
            deepClone1(origin[key], target[key]); //递归
          } else {
            target[key] = origin[key];
          }
        }
      }
      return target;
    }
    
    function deepClone2(data) {
      if (!data || !(data instanceof Object) || typeof data === 'function') {
        return data;
      }
      var constructor = data.constructor;
      var result = new constructor();
      for (var key in data) {
        if (data.hasOwnProperty(key)) {
          result[key] = deepClone2(data[key]);
        }
      }
      return result;
    }
    
    function deepClone3(origin, target) {
      var target = target || {},
        toStr = Object.prototype.toString;
      for (var prop in origin) {
        if (origin.hasOwnProperty(prop)) {
          //不能把原型链上的一起拷贝了
          //判断是元素类型还是引用类型
          if (typeof origin[prop] == 'object' && typeof origin[prop] !== 'null') {
            target[prop] = toStr.call(prop) == '[object Array]' ? [] : {};
            arguments.callee(origin[prop], target[prop]); //递归调用
          } else {
            target[prop] = origin[prop]; //原始类型直接复制
          }
        }
      }
    
      return target;
    }
    function deepClone4(obj) {
      //判断是否是简单数据类型,
      if (typeof obj == 'object') {
        //复杂数据类型
        var result = obj.constructor == Array ? [] : {};
        for (let i in obj) {
          result[i] =
            typeof obj[i] == 'object' && obj[i] !== null
              ? deepClone4(obj[i])
              : obj[i];
        }
      } else {
        //简单数据类型 直接 == 赋值
        var result = obj;
      }
      return result;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72

    推荐使用 deepClone2()

  • 函数的防抖与节流 ?

    点击

    防抖

    所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。(防误触)

    // 延缓执行
    function debounce(func, wait) {
      var timeout;
    
      return function() {
        var context = this;
        var args = arguments;
        if (timeout) clearTimeout(timeout);
    
        timeout = setTimeout(function() {
          func.apply(context, args);
        }, wait);
      };
    }
    // 立即执行
    function debounce(func, wait) {
      var timeout;
    
      return function() {
        var context = this;
        var args = arguments;
    
        if (timeout) clearTimeout(timeout);
    
        var callNow = !timeout;
        timeout = setTimeout(function() {
          timeout = null;
        }, wait);
    
        if (callNow) func.apply(context, args);
      };
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

    节流

    所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。(限制流量)

    // 时间戳
    function throttle(func, wait) {
      var previous = 0;
    
      return function() {
        var now = Date.now();
        var context = this;
        var args = arguments;
        if (now - previous > wait) {
          func.apply(context, args);
          previous = now;
        }
      };
    }
    // 定时器,延迟执行
    function throttle(func, wait) {
      var timeout;
    
      return function() {
        var context = this;
        var args = arguments;
        if (!timeout) {
          timeout = setTimeout(function() {
            timeout = null;
            func.apply(context, args);
          }, wait);
        }
      };
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

# 2019/04/01 - 2019/04/07 ⌚️


  • __proto__prototype 的区别 ?

    点击
    1. 在 JS 里,万物皆对象。方法(Function)是对象,方法的原型(Function.prototype)是对象。因此,它们都会具有对象共有的特点。即:对象具有属性__proto__,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。

    2. 方法(Function)方法这个特殊的对象,除了和其他对象一样有上述 proto 属性之外,还有自己特有的属性——原型属性(prototype),这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)。原型对象也有一个属性,叫做 constructor,这个属性包含了一个指针,指回原构造函数。