乐趣区

关于前端:腾讯前端二面经典面试题持续更新中

程度垂直居中的实现

  • 利用相对定位,先将元素的左上角通过 top:50% 和 left:50% 定位到页面的核心,而后再通过 translate 来调整元素的中心点到页面的核心。该办法须要 思考浏览器兼容问题。
.parent {position: relative;} .child {position: absolute;    left: 50%;    top: 50%;    transform: translate(-50%,-50%);}
  • 利用相对定位,设置四个方向的值都为 0,并将 margin 设置为 auto,因为宽高固定,因而对应方向实现平分,能够实现程度和垂直方向上的居中。该办法实用于 盒子有宽高 的状况:
.parent {position: relative;}

.child {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
  • 利用相对定位,先将元素的左上角通过 top:50% 和 left:50% 定位到页面的核心,而后再通过 margin 负值来调整元素的中心点到页面的核心。该办法实用于 盒子宽高已知 的状况
.parent {position: relative;}

.child {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -50px;     /* 本身 height 的一半 */
    margin-left: -50px;    /* 本身 width 的一半 */
}
  • 应用 flex 布局,通过 align-items:center 和 justify-content:center 设置容器的垂直和程度方向上为居中对齐,而后它的子元素也能够实现垂直和程度的居中。该办法要 思考兼容的问题,该办法在挪动端用的较多:
.parent {
    display: flex;
    justify-content:center;
    align-items:center;
}

寄生组合继承

题目形容: 实现一个你认为不错的 js 继承形式

实现代码如下:

function Parent(name) {
  this.name = name;
  this.say = () => {console.log(111);
  };
}
Parent.prototype.play = () => {console.log(222);
};
function Children(name) {Parent.call(this);
  this.name = name;
}
Children.prototype = Object.create(Parent.prototype);
Children.prototype.constructor = Children;
// let child = new Children("111");
// // console.log(child.name);
// // child.say();
// // child.play();

代码输入后果

Promise.resolve('1')
  .then(res => {console.log(res)
  })
  .finally(() => {console.log('finally')
  })
Promise.resolve('2')
  .finally(() => {console.log('finally2')
      return '我是 finally2 返回的值'
  })
  .then(res => {console.log('finally2 前面的 then 函数', res)
  })

输入后果如下:

1
finally2
finally
finally2 前面的 then 函数 2

.finally()个别用的很少,只有记住以下几点就能够了:

  • .finally()办法不论 Promise 对象最初的状态如何都会执行
  • .finally()办法的回调函数不承受任何的参数,也就是说你在 .finally() 函数中是无奈晓得 Promise 最终的状态是 resolved 还是 rejected
  • 它最终返回的默认会是一个上一次的 Promise 对象值,不过如果抛出的是一个异样则返回异样的 Promise 对象。
  • finally 实质上是 then 办法的特例

.finally()的谬误捕捉:

Promise.resolve('1')
  .finally(() => {console.log('finally1')
    throw new Error('我是 finally 中抛出的异样')
  })
  .then(res => {console.log('finally 前面的 then 函数', res)
  })
  .catch(err => {console.log('捕捉谬误', err)
  })

输入后果为:

'finally1'
'捕捉谬误' Error: 我是 finally 中抛出的异样

过程与线程的概念

从实质上说,过程和线程都是 CPU 工作工夫片的一个形容:

  • 过程形容了 CPU 在运行指令及加载和保留上下文所需的工夫,放在利用上来说就代表了一个程序。
  • 线程是过程中的更小单位,形容了执行一段指令所需的工夫。

过程是资源分配的最小单位,线程是 CPU 调度的最小单位。

一个过程就是一个程序的运行实例。具体解释就是,启动一个程序的时候,操作系统会为该程序创立一块内存,用来寄存代码、运行中的数据和一个执行工作的主线程,咱们把这样的一个运行环境叫 过程 过程是运行在虚拟内存上的,虚拟内存是用来解决用户对硬件资源的有限需要和无限的硬件资源之间的矛盾的。从操作系统角度来看,虚拟内存即交换文件;从处理器角度看,虚拟内存即虚拟地址空间。

如果程序很多时,内存可能会不够,操作系统为每个过程提供一套独立的虚拟地址空间,从而使得同一块物理内存在不同的过程中能够对应到不同或雷同的虚拟地址,变相的减少了程序能够应用的内存。

过程和线程之间的关系有以下四个特点:

(1)过程中的任意一线程执行出错,都会导致整个过程的解体。

(2)线程之间共享过程中的数据。

(3)当一个过程敞开之后,操作系统会回收过程所占用的内存, 当一个过程退出时,操作系统会回收该过程所申请的所有资源;即便其中任意线程因为操作不当导致内存透露,当过程退出时,这些内存也会被正确回收。

(4)过程之间的内容互相隔离。 过程隔离就是为了使操作系统中的过程互不烦扰,每一个过程只能拜访本人占有的数据,也就避免出现过程 A 写入数据到过程 B 的状况。正是因为过程之间的数据是严格隔离的,所以一个过程如果解体了,或者挂起了,是不会影响到其余过程的。如果过程之间须要进行数据的通信,这时候,就须要应用用于过程间通信的机制了。

Chrome 浏览器的架构图:从图中能够看出,最新的 Chrome 浏览器包含:

  • 1 个浏览器主过程
  • 1 个 GPU 过程
  • 1 个网络过程
  • 多个渲染过程
  • 多个插件过程

这些过程的性能:

  • 浏览器过程:次要负责界面显示、用户交互、子过程治理,同时提供存储等性能。
  • 渲染过程:外围工作是将 HTML、CSS 和 JavaScript 转换为用户能够与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该过程中,默认状况下,Chrome 会为每个 Tab 标签创立一个渲染过程。出于平安思考,渲染过程都是运行在沙箱模式下。
  • GPU 过程:其实,GPU 的应用初衷是为了实现 3D CSS 的成果,只是随后网页、Chrome 的 UI 界面都抉择采纳 GPU 来绘制,这使得 GPU 成为浏览器广泛的需要。最初,Chrome 在其多过程架构上也引入了 GPU 过程。
  • 网络过程:次要负责页面的网络资源加载,之前是作为一个模块运行在浏览器过程外面的,直至最近才独立进去,成为一个独自的过程。
  • 插件过程:次要是负责插件的运行,因插件易解体,所以须要通过插件过程来隔离,以保障插件过程解体不会对浏览器和页面造成影响。

所以,关上一个网页,起码须要四个过程:1 个网络过程、1 个浏览器过程、1 个 GPU 过程以及 1 个渲染过程。如果关上的页面有运行插件的话,还须要再加上 1 个插件过程。

尽管多过程模型晋升了浏览器的稳定性、流畅性和安全性,但同样不可避免地带来了一些问题:

  • 更高的资源占用:因为每个过程都会蕴含公共根底构造的正本(如 JavaScript 运行环境),这就意味着浏览器会耗费更多的内存资源。
  • 更简单的体系架构:浏览器各模块之间耦合性高、扩展性差等问题,会导致当初的架构曾经很难适应新的需要了。

事件是什么?事件模型?

事件是用户操作网页时产生的交互动作,比方 click/move,事件除了用户触发的动作外,还能够是文档加载,窗口滚动和大小调整。事件被封装成一个 event 对象,蕴含了该事件产生时的所有相干信息(event 的属性)以及能够对事件进行的操作(event 的办法)。

事件是用户操作网页时产生的交互动作或者网页自身的一些操作,古代浏览器一共有三种事件模型:

  • DOM0 级事件模型,这种模型不会流传,所以没有事件流的概念,然而当初有的浏览器反对以冒泡的形式实现,它能够在网页中间接定义监听函数,也能够通过 js 属性来指定监听函数。所有浏览器都兼容这种形式。间接在 dom 对象上注册事件名称,就是 DOM0 写法。
  • IE 事件模型,在该事件模型中,一次事件共有两个过程,事件处理阶段和事件冒泡阶段。事件处理阶段会首先执行指标元素绑定的监听事件。而后是事件冒泡阶段,冒泡指的是事件从指标元素冒泡到 document,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过 attachEvent 来增加监听函数,能够增加多个监听函数,会按程序顺次执行。
  • DOM2 级事件模型,在该事件模型中,一次事件共有三个过程,第一个过程是事件捕捉阶段。捕捉指的是事件从 document 始终向下流传到指标元素,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。前面两个阶段和 IE 事件模型的两个阶段雷同。这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数能够指定事件是否在捕捉阶段执行。

手写题:数组扁平化

function flatten(arr) {let result = [];

  for (let i = 0; i < arr.length; i++) {if (Array.isArray(arr[i])) {result = result.concat(flatten(arr[i]));
    } else {result = result.concat(arr[i]);
    }
  }

  return result;
}

const a = [1, [2, [3, 4]]];
console.log(flatten(a));

参考 前端进阶面试题具体解答

事件是如何实现的?

基于公布订阅模式,就是在浏览器加载的时候会读取事件相干的代码,然而只有理论等到具体的事件触发的时候才会执行。

比方点击按钮,这是个事件(Event),而负责处理事件的代码段通常被称为事件处理程序(Event Handler),也就是「启动对话框的显示」这个动作。

在 Web 端,咱们常见的就是 DOM 事件:

  • DOM0 级事件,间接在 html 元素上绑定 on-event,比方 onclick,勾销的话,dom.onclick = null,同一个事件只能有一个处理程序,前面的会笼罩后面的。
  • DOM2 级事件,通过 addEventListener 注册事件,通过 removeEventListener 来删除事件,一个事件能够有多个事件处理程序,按程序执行,捕捉事件和冒泡事件
  • DOM3 级事件,减少了事件类型,比方 UI 事件,焦点事件,鼠标事件

代码输入后果

var length = 10;
function fn() {console.log(this.length);
}

var obj = {
  length: 5,
  method: function(fn) {fn();
    arguments[0]();}
};

obj.method(fn, 1);

输入后果:10 2

解析:

  1. 第一次执行 fn(),this 指向 window 对象,输入 10。
  2. 第二次执行 arguments[0],相当于 arguments 调用办法,this 指向 arguments,而这里传了两个参数,故输入 arguments 长度为 2。

localStorage sessionStorage cookies 有什么区别?

localStorage: 以键值对的形式存储 贮存工夫没有限度 永恒失效 除非本人删除记录
sessionStorage:当页面敞开后被清理与其余相比不能同源窗口共享 是会话级别的存储形式
cookies 数据不能超过 4k 同时因为每次 http 申请都会携带 cookie 所有 cookie 只适宜保留很小的数据 如会话标识

代码输入后果

 var a = 10; 
 var obt = { 
   a: 20, 
   fn: function(){ 
     var a = 30; 
     console.log(this.a)
   } 
 }
 obt.fn();  // 20
 obt.fn.call(); // 10
 (obt.fn)(); // 20

输入后果:20 10 20

解析:

  1. obt.fn(),fn 是由 obt 调用的,所以其 this 指向 obt 对象,会打印出 20;
  2. obt.fn.call(),这里 call 的参数啥都没写,就示意 null,咱们晓得如果 call 的参数为 undefined 或 null,那么 this 就会指向全局对象 this,所以会打印出 10;
  3. (obt.fn)(),这里给表达式加了括号,而括号的作用是扭转表达式的运算程序,而在这里加与不加括号并无影响;相当于 obt.fn(),所以会打印出 20;

事件委托的应用场景

场景:给页面的所有的 a 标签增加 click 事件,代码如下:

document.addEventListener("click", function(e) {if (e.target.nodeName == "A")
        console.log("a");
}, false);

然而这些 a 标签可能蕴含一些像 span、img 等元素,如果点击到了这些 a 标签中的元素,就不会触发 click 事件,因为事件绑定上在 a 标签元素上,而触发这些外部的元素时,e.target 指向的是触发 click 事件的元素(span、img 等其余元素)。

这种状况下就能够应用事件委托来解决,将事件绑定在 a 标签的外部元素上,当点击它的时候,就会逐级向上查找,晓得找到 a 标签为止,代码如下:

document.addEventListener("click", function(e) {
    var node = e.target;
    while (node.parentNode.nodeName != "BODY") {if (node.nodeName == "A") {console.log("a");
            break;
        }
        node = node.parentNode;
    }
}, false);

如何提⾼ webpack 的构建速度?

  1. 多⼊⼝状况下,使⽤ CommonsChunkPlugin 来提取公共代码
  2. 通过 externals 配置来提取常⽤库
  3. 利⽤ DllPlugin 和 DllReferencePlugin 预编译资源模块 通过 DllPlugin 来对那些咱们引⽤然而相对不会批改的 npm 包来进⾏预编译,再通过 DllReferencePlugin 将预编译的模块加载进来。
  4. 使⽤ Happypack 实现多线程减速编译
  5. 使⽤ webpack-uglify-parallel 来晋升 uglifyPlugin 的压缩速度。原理上 webpack-uglify-parallel 采⽤了多核并⾏压缩来晋升压缩速度
  6. 使⽤ Tree-shaking 和 Scope Hoisting 来剔除多余代码

PWA 应用过吗?serviceWorker 的应用原理是啥?

渐进式网络应用(PWA)是谷歌在 2015 年底提出的概念。基本上算是 web 应用程序,但在外观和感觉上与 原生 app相似。反对 PWA 的网站能够提供脱机工作、推送告诉和设施硬件拜访等性能。

Service Worker是浏览器在后盾独立于网页运行的脚本,它关上了通向不须要网页或用户交互的性能的大门。当初,它们已包含如推送告诉和后盾同步等性能。未来,Service Worker将会反对如定期同步或天文围栏等其余性能。本教程探讨的外围性能是拦挡和解决网络申请,包含通过程序来治理缓存中的响应。

冒泡排序 – 工夫复杂度 n^2

题目形容: 实现一个冒泡排序

实现代码如下:

function bubbleSort(arr) {
  // 缓存数组长度
  const len = arr.length;
  // 外层循环用于管制从头到尾的比拟 + 替换到底有多少轮
  for (let i = 0; i < len; i++) {
    // 内层循环用于实现每一轮遍历过程中的反复比拟 + 替换
    for (let j = 0; j < len - 1; j++) {
      // 若相邻元素后面的数比前面的大
      if (arr[j] > arr[j + 1]) {
        // 替换两者
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  // 返回数组
  return arr;
}
// console.log(bubbleSort([3, 6, 2, 4, 1]));

Promise 以及相干办法的实现

题目形容: 手写 Promise 以及 Promise.all Promise.race 的实现

实现代码如下:

class Mypromise {constructor(fn) {
    // 示意状态
    this.state = "pending";
    // 示意 then 注册的胜利函数
    this.successFun = [];
    // 示意 then 注册的失败函数
    this.failFun = [];

    let resolve = (val) => {
      // 放弃状态扭转不可变(resolve 和 reject 只准触发一种)if (this.state !== "pending") return;

      // 胜利触发机会  扭转状态 同时执行在 then 注册的回调事件
      this.state = "success";
      // 为了保障 then 事件先注册(次要是思考在 promise 外面写同步代码)promise 标准 这里为模仿异步
      setTimeout(() => {
        // 执行以后事件外面所有的注册函数
        this.successFun.forEach((item) => item.call(this, val));
      });
    };

    let reject = (err) => {if (this.state !== "pending") return;
      // 失败触发机会  扭转状态 同时执行在 then 注册的回调事件
      this.state = "fail";
      // 为了保障 then 事件先注册(次要是思考在 promise 外面写同步代码)promise 标准 这里模仿异步
      setTimeout(() => {this.failFun.forEach((item) => item.call(this, err));
      });
    };
    // 调用函数
    try {fn(resolve, reject);
    } catch (error) {reject(error);
    }
  }

  // 实例办法 then

  then(resolveCallback, rejectCallback) {
    // 判断回调是否是函数
    resolveCallback =
      typeof resolveCallback !== "function" ? (v) => v : resolveCallback;
    rejectCallback =
      typeof rejectCallback !== "function"
        ? (err) => {throw err;}
        : rejectCallback;
    // 为了放弃链式调用  持续返回 promise
    return new Mypromise((resolve, reject) => {
      // 将回调注册到 successFun 事件汇合外面去
      this.successFun.push((val) => {
        try {
          //    执行回调函数
          let x = resolveCallback(val);
          //(最难的一点)// 如果回调函数后果是一般值 那么就 resolve 进来给下一个 then 链式调用  如果是一个 promise 对象(代表又是一个异步)那么调用 x 的 then 办法 将 resolve 和 reject 传进去 等到 x 外部的异步 执行结束的时候(状态实现)就会主动执行传入的 resolve 这样就管制了链式调用的程序
          x instanceof Mypromise ? x.then(resolve, reject) : resolve(x);
        } catch (error) {reject(error);
        }
      });

      this.failFun.push((val) => {
        try {
          //    执行回调函数
          let x = rejectCallback(val);
          x instanceof Mypromise ? x.then(resolve, reject) : reject(x);
        } catch (error) {reject(error);
        }
      });
    });
  }
  // 静态方法
  static all(promiseArr) {let result = [];
    // 申明一个计数器 每一个 promise 返回就加一
    let count = 0;
    return new Mypromise((resolve, reject) => {for (let i = 0; i < promiseArr.length; i++) {
      // 这里用 Promise.resolve 包装一下 避免不是 Promise 类型传进来
        Promise.resolve(promiseArr[i]).then((res) => {// 这里不能间接 push 数组  因为要管制程序一一对应(感激评论区斧正)
            result[i] = res;
            count++;
            // 只有全副的 promise 执行胜利之后才 resolve 进来
            if (count === promiseArr.length) {resolve(result);
            }
          },
          (err) => {reject(err);
          }
        );
      }
    });
  }
  // 静态方法
  static race(promiseArr) {return new Mypromise((resolve, reject) => {for (let i = 0; i < promiseArr.length; i++) {Promise.resolve(promiseArr[i]).then((res) => {
            //promise 数组只有有任何一个 promise 状态变更  就能够返回
            resolve(res);
          },
          (err) => {reject(err);
          }
        );
      }
    });
  }
}

// 应用
// let promise1 = new Mypromise((resolve, reject) => {//   setTimeout(() => {//     resolve(123);
//   }, 2000);
// });
// let promise2 = new Mypromise((resolve, reject) => {//   setTimeout(() => {//     resolve(1234);
//   }, 1000);
// });

// Mypromise.all([promise1,promise2]).then(res=>{//   console.log(res);
// })

// Mypromise.race([promise1, promise2]).then(res => {//   console.log(res);
// });

// promise1
//   .then(
//     res => {//       console.log(res); // 过两秒输入 123
//       return new Mypromise((resolve, reject) => {//         setTimeout(() => {//           resolve("success");
//         }, 1000);
//       });
//     },
//     err => {//       console.log(err);
//     }
//   )
//   .then(
//     res => {//       console.log(res); // 再过一秒输入 success
//     },
//     err => {//       console.log(err);
//     }
//   );

扩大思考: 如何勾销 promise

Promise.race()办法能够用来竞争 Promise
能够借助这个个性 本人包装一个 空的 Promise 与要发动的 Promise 来实现

function wrap(pro) {let obj = {};
  // 结构一个新的 promise 用来竞争
  let p1 = new Promise((resolve, reject) => {
    obj.resolve = resolve;
    obj.reject = reject;
  });

  obj.promise = Promise.race([p1, pro]);
  return obj;
}

let testPro = new Promise((resolve, reject) => {setTimeout(() => {resolve(123);
  }, 1000);
});

let wrapPro = wrap(testPro);
wrapPro.promise.then((res) => {console.log(res);
});
wrapPro.resolve("被拦挡了");

为什么 0.1 + 0.2 != 0.3,请详述理由

因为 JS 采纳 IEEE 754 双精度版本(64 位),并且只有采纳 IEEE 754 的语言都有该问题。

咱们都晓得计算机示意十进制是采纳二进制示意的,所以 0.1 在二进制示意为

// (0011) 示意循环
0.1 = 2^-4 * 1.10011(0011)

那么如何失去这个二进制的呢,咱们能够来演算下

小数算二进制和整数不同。乘法计算时,只计算小数位,整数位用作每一位的二进制,并且失去的第一位为最高位。所以咱们得出 0.1 = 2^-4 * 1.10011(0011),那么 0.2 的演算也根本如上所示,只须要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)

回来持续说 IEEE 754 双精度。六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.10.2 都是有限循环的二进制了,所以在小数位开端处须要判断是否进位(就和十进制的四舍五入一样)。

所以 2^-4 * 1.10011...001 进位后就变成了 2^-4 * 1.10011(0011 * 12 次)010。那么把这两个二进制加起来会得出 2^-2 * 1.0011(0011 * 11 次)0100 , 这个值算成十进制就是 0.30000000000000004

上面说一下原生解决办法,如下代码所示

parseFloat((0.1 + 0.2).toFixed(10))

首屏和白屏工夫如何计算

首屏工夫的计算,能够由 Native WebView 提供的相似 onload 的办法实现,在 ios 下对应的是 webViewDidFinishLoad,在 android 下对应的是 onPageFinished 事件。

白屏的定义有多种。能够认为“没有任何内容”是白屏,能够认为“网络或服务异样”是白屏,能够认为“数据加载中”是白屏,能够认为“图片加载不进去”是白屏。场景不同,白屏的计算形式就不雷同。

办法 1:当页面的元素数小于 x 时,则认为页面白屏。比方“没有任何内容”,能够获取页面的 DOM 节点数,判断 DOM 节点数少于某个阈值 X,则认为白屏。办法 2:当页面呈现业务定义的错误码时,则认为是白屏。比方“网络或服务异样”。办法 3:当页面呈现业务定义的特征值时,则认为是白屏。比方“数据加载中”。

继承

原型链继承

function Animal() {this.colors = ['black', 'white']
}
Animal.prototype.getColor = function() {return this.colors}
function Dog() {}
Dog.prototype =  new Animal()

let dog1 = new Dog()
dog1.colors.push('brown')
let dog2 = new Dog()
console.log(dog2.colors)  // ['black', 'white', 'brown']

原型链继承存在的问题:

  • 问题 1:原型中蕴含的援用类型属性将被所有实例共享;
  • 问题 2:子类在实例化的时候不能给父类构造函数传参;

借用构造函数实现继承

function Animal(name) {
    this.name = name
    this.getName = function() {return this.name}
}
function Dog(name) {Animal.call(this, name)
}
Dog.prototype =  new Animal()

借用构造函数实现继承解决了原型链继承的 2 个问题:援用类型共享问题以及传参问题。然而因为办法必须定义在构造函数中,所以会导致每次创立子类实例都会创立一遍办法。

组合继承

组合继承联合了原型链和盗用构造函数,将两者的长处集中了起来。根本的思路是应用原型链继承原型上的属性和办法,而通过盗用构造函数继承实例属性。这样既能够把办法定义在原型上以实现重用,又能够让每个实例都有本人的属性。

function Animal(name) {
    this.name = name
    this.colors = ['black', 'white']
}
Animal.prototype.getName = function() {return this.name}
function Dog(name, age) {Animal.call(this, name)
    this.age = age
}
Dog.prototype =  new Animal()
Dog.prototype.constructor = Dog

let dog1 = new Dog('奶昔', 2)
dog1.colors.push('brown')
let dog2 = new Dog('哈赤', 1)
console.log(dog2) 
// {name: "哈赤", colors: ["black", "white"], age: 1 }

寄生式组合继承

组合继承曾经绝对欠缺了,但还是存在问题,它的问题就是调用了 2 次父类构造函数,第一次是在 new Animal(),第二次是在 Animal.call() 这里。

所以解决方案就是不间接调用父类构造函数给子类原型赋值,而是通过创立空函数 F 获取父类原型的正本。

寄生式组合继承写法上和组合继承根本相似,区别是如下这里:

- Dog.prototype =  new Animal()
- Dog.prototype.constructor = Dog

+ function F() {}
+ F.prototype = Animal.prototype
+ let f = new F()
+ f.constructor = Dog
+ Dog.prototype = f

略微封装下下面增加的代码后:

function object(o) {function F() {}
    F.prototype = o
    return new F()}
function inheritPrototype(child, parent) {let prototype = object(parent.prototype)
    prototype.constructor = child
    child.prototype = prototype
}
inheritPrototype(Dog, Animal)

如果你厌弃下面的代码太多了,还能够基于组合继承的代码改成最简略的寄生式组合继承:

- Dog.prototype =  new Animal()
- Dog.prototype.constructor = Dog

+ Dog.prototype =  Object.create(Animal.prototype)
+ Dog.prototype.constructor = Dog

class 实现继承

class Animal {constructor(name) {this.name = name} 
    getName() {return this.name}
}
class Dog extends Animal {constructor(name, age) {super(name)
        this.age = age
    }
}

原函数形参不定长(此时 fn.length 为 0)

function curry(fn) {
    // 保留参数,除去第一个函数参数
    let args = [].slice.call(arguments, 1);
    // 返回一个新函数
    let curried = function () {
        // 新函数调用时会持续传参
        let allArgs = [...args, ...arguments];
        return curry(fn, ...allArgs);
    };
    // 利用 toString 隐式转换的个性,当最初执行函数时,会隐式转换
    curried.toString = function () {return fn(...args);
    };
    return curried;
}

// 测试
function add(...args) {return args.reduce((pre, cur) => pre + cur, 0);
}
console.log(add(1, 2, 3, 4));
let addCurry = curry(add);
console.log(addCurry(1)(2)(3) == 6); // true
console.log(addCurry(1, 2, 3)(4) == 10); // true
console.log(addCurry(2, 6)(1).toString()); // 9
console.log(addCurry(2, 6)(1, 8)); // 打印 curried 函数

深浅拷贝

浅拷贝:只思考对象类型。

function shallowCopy(obj) {if (typeof obj !== 'object') return

    let newObj = obj instanceof Array ? [] : {}
    for (let key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] = obj[key]
        }
    }
    return newObj
}

简略版深拷贝:只思考一般对象属性,不思考内置对象和函数。

function deepClone(obj) {if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {if (obj.hasOwnProperty(key)) {newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
        }
    }
    return newObj;
}

简单版深克隆:基于简略版的根底上,还思考了内置对象比方 Date、RegExp 等对象和函数以及解决了循环援用的问题。

const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;

function deepClone(target, map = new WeakMap()) {if (map.get(target)) {return target;}
    // 获取以后值的构造函数:获取它的类型
    let constructor = target.constructor;
    // 检测以后对象 target 是否与正则、日期格局对象匹配
    if (/^(RegExp|Date)$/i.test(constructor.name)) {// 创立一个新的非凡对象 (正则类 / 日期类) 的实例
        return new constructor(target);  
    }
    if (isObject(target)) {map.set(target, true);  // 为循环援用的对象做标记
        const cloneTarget = Array.isArray(target) ? [] : {};
        for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = deepClone(target[prop], map);
            }
        }
        return cloneTarget;
    } else {return target;}
}
退出移动版