关于前端:前端一面必会面试题边面边更

3次阅读

共计 26571 个字符,预计需要花费 67 分钟才能阅读完成。

哪些状况会导致内存透露

以下四种状况会造成内存的透露:

  • 意外的全局变量: 因为应用未声明的变量,而意外的创立了一个全局变量,而使这个变量始终留在内存中无奈被回收。
  • 被忘记的计时器或回调函数: 设置了 setInterval 定时器,而遗记勾销它,如果循环函数有对外部变量的援用的话,那么这个变量会被始终留在内存中,而无奈被回收。
  • 脱离 DOM 的援用: 获取一个 DOM 元素的援用,而前面这个元素被删除,因为始终保留了对这个元素的援用,所以它也无奈被回收。
  • 闭包: 不合理的应用闭包,从而导致某些变量始终被留在内存当中。

常见的 CSS 布局单位

罕用的布局单位包含像素(px),百分比(%),emremvw/vh

(1)像素px)是页面布局的根底,一个像素示意终端(电脑、手机、平板等)屏幕所能显示的最小的区域,像素分为两种类型:CSS 像素和物理像素:

  • CSS 像素:为 web 开发者提供,在 CSS 中应用的一个形象单位;
  • 物理像素:只与设施的硬件密度无关,任何设施的物理像素都是固定的。

(2)百分比%),当浏览器的宽度或者高度发生变化时,通过百分比单位能够使得浏览器中的组件的宽和高随着浏览器的变动而变动,从而实现响应式的成果。个别认为子元素的百分比绝对于间接父元素。

(3)em 和 rem绝对于 px 更具灵活性,它们都是绝对长度单位,它们之间的区别:em 绝对于父元素,rem 绝对于根元素。

  • em: 文本绝对长度单位。绝对于以后对象内文本的字体尺寸。如果以后行内文本的字体尺寸未被人为设置,则绝对于浏览器的默认字体尺寸(默认 16px)。(绝对父元素的字体大小倍数)。
  • rem: rem 是 CSS3 新增的一个绝对单位,绝对于根元素(html 元素)的 font-size 的倍数。作用:利用 rem 能够实现简略的响应式布局,能够利用 html 元素中字体的大小与屏幕间的比值来设置 font-size 的值,以此实现当屏幕分辨率变动时让元素也随之变动。

(4)vw/vh是与视图窗口无关的单位,vw 示意绝对于视图窗口的宽度,vh 示意绝对于视图窗口高度,除了 vw 和 vh 外,还有 vmin 和 vmax 两个相干的单位。

  • vw:绝对于视窗的宽度,视窗宽度是 100vw;
  • vh:绝对于视窗的高度,视窗高度是 100vh;
  • vmin:vw 和 vh 中的较小值;
  • vmax:vw 和 vh 中的较大值;

vw/vh 和百分比很相似,两者的区别:

  • 百分比(%):大部分绝对于先人元素,也有绝对于本身的状况比方(border-radius、translate 等)
  • vw/vm:绝对于视窗的尺寸

position 的属性有哪些,区别是什么

position 有以下属性值:

属性值 概述
absolute 生成相对定位的元素,绝对于 static 定位以外的一个父元素进行定位。元素的地位通过 left、top、right、bottom 属性进行规定。
relative 生成绝对定位的元素,绝对于其原来的地位进行定位。元素的地位通过 left、top、right、bottom 属性进行规定。
fixed 生成相对定位的元素,指定元素绝对于屏幕视⼝(viewport)的地位来指定元素地位。元素的地位在屏幕滚动时不会扭转,⽐如回到顶部的按钮⼀般都是⽤此定位⽅式。
static 默认值,没有定位,元素呈现在失常的文档流中,会疏忽 top, bottom, left, right 或者 z-index 申明,块级元素从上往下纵向排布,⾏级元素从左向右排列。
inherit 规定从父元素继承 position 属性的值

后面三者的定位形式如下:

  • relative: 元素的定位永远是绝对于元素本身地位的,和其余元素没关系,也不会影响其余元素。
  • fixed: 元素的定位是绝对于 window(或者 iframe)边界的,和其余元素没有关系。然而它具备破坏性,会导致其余元素地位的变动。
  • absolute: 元素的定位绝对于前两者要简单许多。如果为 absolute 设置了 top、left,浏览器会依据什么去确定它的纵向和横向的偏移量呢?答案是浏览器会递归查找该元素的所有父元素,如果找到一个设置了 position:relative/absolute/fixed 的元素,就以该元素为基准定位,如果没找到,就以浏览器边界定位。如下两个图所示:

程度垂直居中的实现

  • 利用相对定位,先将元素的左上角通过 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;
}

说一下 HTML5 drag API

  • dragstart:事件主体是被拖放元素,在开始拖放被拖放元素时触发。
  • darg:事件主体是被拖放元素,在正在拖放被拖放元素时触发。
  • dragenter:事件主体是指标元素,在被拖放元素进入某元素时触发。
  • dragover:事件主体是指标元素,在被拖放在某元素内挪动时触发。
  • dragleave:事件主体是指标元素,在被拖放元素移出指标元素是触发。
  • drop:事件主体是指标元素,在指标元素齐全承受被拖放元素时触发。
  • dragend:事件主体是被拖放元素,在整个拖放操作完结时触发。

设置小于 12px 的字体

在谷歌下 css 设置字体大小为 12px 及以下时,显示都是一样大小,都是默认 12px。

解决办法:

  • 应用 Webkit 的内核的 -webkit-text-size-adjust 的公有 CSS 属性来解决,只有加了 -webkit-text-size-adjust:none; 字体大小就不受限制了。然而 chrome 更新到 27 版本之后就不能够用了。所以高版本 chrome 谷歌浏览器曾经不再反对 -webkit-text-size-adjust 款式,所以要应用时候慎用。
  • 应用 css3 的 transform 缩放属性 -webkit-transform:scale(0.5); 留神 -webkit-transform:scale(0.75); 膨胀的是整个元素的大小,这时候,如果是内联元素,必须要将内联元素转换成块元素,能够应用 display:block/inline-block/…;
  • 应用图片:如果是内容固定不变状况下,应用将小于 12px 文字内容切出做图片,这样不影响兼容也不影响好看。

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

link 和 @import 的区别

两者都是内部援用 CSS 的形式,它们的区别如下:

  • link 是 XHTML 标签,除了加载 CSS 外,还能够定义 RSS 等其余事务;@import 属于 CSS 领域,只能加载 CSS。
  • link 援用 CSS 时,在页面载入时同时加载;@import 须要页面网页齐全载入当前加载。
  • link 是 XHTML 标签,无兼容问题;@import 是在 CSS2.1 提出的,低版本的浏览器不反对。
  • link 反对应用 Javascript 管制 DOM 去扭转款式;而 @import 不反对。

对 BFC 的了解,如何创立 BFC

先来看两个相干的概念:

  • Box: Box 是 CSS 布局的对象和根本单位,⼀个⻚⾯是由很多个 Box 组成的,这个 Box 就是咱们所说的盒模型。
  • Formatting context:块级高低⽂格式化,它是⻚⾯中的⼀块渲染区域,并且有⼀套渲染规定,它决定了其⼦元素将如何定位,以及和其余元素的关系和互相作⽤。

块格式化上下文(Block Formatting Context,BFC)是 Web 页面的可视化 CSS 渲染的一部分,是布局过程中生成块级盒子的区域,也是浮动元素与其余元素的交互限定区域。

艰深来讲:BFC 是一个独立的布局环境,能够了解为一个容器,在这个容器中依照肯定规定进行物品摆放,并且不会影响其它环境中的物品。如果一个元素合乎触发 BFC 的条件,则 BFC 中的元素布局不受内部影响。

创立 BFC 的条件:

  • 根元素:body;
  • 元素设置浮动:float 除 none 以外的值;
  • 元素设置相对定位:position (absolute、fixed);
  • display 值为:inline-block、table-cell、table-caption、flex 等;
  • overflow 值为:hidden、auto、scroll;

BFC 的特点:

  • 垂直方向上,自上而下排列,和文档流的排列形式统一。
  • 在 BFC 中高低相邻的两个容器的 margin 会重叠
  • 计算 BFC 的高度时,须要计算浮动元素的高度
  • BFC 区域不会与浮动的容器产生重叠
  • BFC 是独立的容器,容器外部元素不会影响内部元素
  • 每个元素的左 margin 值和容器的左 border 相接触

BFC 的作用:

  • 解决 margin 的重叠问题:因为 BFC 是一个独立的区域,外部的元素和内部的元素互不影响,将两个元素变为两个 BFC,就解决了 margin 重叠的问题。
  • 解决高度塌陷的问题:在对子元素设置浮动后,父元素会产生高度塌陷,也就是父元素的高度变为 0。解决这个问题,只须要把父元素变成一个 BFC。罕用的方法是给父元素设置overflow:hidden
  • 创立自适应两栏布局:能够用来创立自适应两栏布局:右边的宽度固定,左边的宽度自适应。
.left{
     width: 100px;
     height: 200px;
     background: red;
     float: left;
 }
 .right{
     height: 300px;
     background: blue;
     overflow: hidden;
 }

<div class="left"></div>
<div class="right"></div>

左侧设置float:left,右侧设置overflow: hidden。这样左边就触发了 BFC,BFC 的区域不会与浮动元素产生重叠,所以两侧就不会产生重叠,实现了自适应两栏布局。

DOM 节点操作

(1)创立新节点

createDocumentFragment()    // 创立一个 DOM 片段
createElement()   // 创立一个具体的元素
createTextNode()   // 创立一个文本节点

(2)增加、移除、替换、插入

appendChild(node)
removeChild(node)
replaceChild(new,old)
insertBefore(new,old)

(3)查找

getElementById();
getElementsByName();
getElementsByTagName();
getElementsByClassName();
querySelector();
querySelectorAll();

(4)属性操作

getAttribute(key);
setAttribute(key, value);
hasAttribute(key);
removeAttribute(key);

CSS 动画和过渡

animation / keyframes

  • animation-name: 动画名称,对应@keyframes
  • animation-duration: 距离
  • animation-timing-function: 曲线
  • animation-delay: 提早
  • animation-iteration-count: 次数

    • infinite: 循环动画
  • animation-direction: 方向

    • alternate: 反向播放
  • animation-fill-mode: 静止模式

    • forwards: 进行时,保留最初一帧
    • backwards: 进行时,回到第一帧
    • both: 同时使用 forwards / backwards
  • 罕用钩子: animationend

动画属性: 尽量应用动画属性进行动画,能领有较好的性能体现

  • translate
  • scale
  • rotate
  • skew
  • opacity
  • color

transform

  • 位移属性 translate(x , y)
  • 旋转属性 rotate()
  • 缩放属性 scale()
  • 歪斜属性 skew()

transition

  • transition-property(过渡的属性的名称)。
  • transition-duration(定义过渡成果破费的工夫, 默认是 0)。
  • transition-timing-function:linear(匀速) ease(慢速开始,而后变快,而后慢速完结)(规定过渡成果的工夫曲线,最罕用的是这两个)。
  • transition-delay(规定过渡成果何时开始。默认是 0)

般状况下,咱们都是写一起的,比方:transition:width 2s ease 1s

关键帧动画 animation

一个关键帧动画,起码蕴含两局部,animation 属性及属性值(动画的名称和运行形式运行工夫等)。@keyframes(规定动画的具体实现过程)

animation 属性能够拆分为

  • animation-name 规定 @keyframes 动画的名称。
  • animation-duration 规定动画实现一个周期所破费的秒或毫秒。默认是 0
  • animation-timing-function 规定动画的速度曲线。默认是“ease”,罕用的还有linear,同transtion
  • animation-delay 规定动画何时开始。默认是 0。
  • animation-iteration-count 规定动画被播放的次数。默认是 1,但咱们个别用infinite,始终播放

@keyframes 的应用办法,能够是 from->to(等同于 0% 和 100%),也能够是从0%->100% 之间任意个的分层设置。咱们通过上面一个略微简单点的 demo 来看一下,基本上用到了下面说到的大部分常识

eg:
   @keyframes mymove
  {from {top:0px;}
      to {top:200px;}
  }

/* 等同于:*/

@keyframes mymove
{0%   {top:0px;}
 25%  {top:200px;}
 50%  {top:100px;}
 75%  {top:200px;}
 100% {top:0px;}
}

用 css3 动画使一个图片旋转

#loader {

    display: block;

    position: relative;

    -webkit-animation: spin 2s linear infinite;

    animation: spin 2s linear infinite;

}

@-webkit-keyframes spin {

    0%   {-webkit-transform: rotate(0deg);

        -ms-transform: rotate(0deg);

        transform: rotate(0deg);

    }

    100% {-webkit-transform: rotate(360deg);

        -ms-transform: rotate(360deg);

        transform: rotate(360deg);

    }

}

@keyframes spin {

    0%   {-webkit-transform: rotate(0deg);

        -ms-transform: rotate(0deg);

        transform: rotate(0deg);

    }

    100% {-webkit-transform: rotate(360deg);

        -ms-transform: rotate(360deg);

        transform: rotate(360deg);

    }

}

template 预编译是什么

对于 Vue 组件来说,模板编译只会在组件实例化的时候编译一次,生成渲染函数之后在也不会进行编译。因而,编译对组件的 runtime 是一种性能损耗。

而模板编译的目标仅仅是将 template 转化为 render function,这个过程,正好能够在我的项目构建的过程中实现,这样能够让理论组件在 runtime 时间接跳过模板渲染,进而晋升性能,这个在我的项目构建的编译 template 的过程,就是预编译。

viewport

<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
    // width    设置 viewport 宽度,为一个正整数,或字符串‘device-width’// device-width  设施宽度
    // height   设置 viewport 高度,个别设置了宽度,会主动解析出高度,能够不必设置
    // initial-scale    默认缩放比例(初始缩放比例),为一个数字,能够带小数
    // minimum-scale    容许用户最小缩放比例,为一个数字,能够带小数
    // maximum-scale    容许用户最大缩放比例,为一个数字,能够带小数
    // user-scalable    是否容许手动缩放
  • 延长发问

    • 怎么解决 挪动端 1px 被 渲染成 2px问题

部分解决

  • meta标签中的 viewport属性,initial-scale 设置为 1
  • rem依照设计稿规范走,外加利用transfromescale(0.5) 放大一倍即可;

全局解决

  • mate标签中的 viewport属性,initial-scale 设置为 0.5
  • rem 依照设计稿规范走即可

深浅拷贝

1. 浅拷贝的原理和实现

本人创立一个新的对象,来承受你要从新复制或援用的对象值。如果对象属性是根本的数据类型,复制的就是根本类型的值给新对象;但如果属性是援用数据类型,复制的就是内存中的地址,如果其中一个对象扭转了这个内存中的地址,必定会影响到另一个对象

办法一:object.assign

object.assign是 ES6 中 object 的一个办法,该办法能够用于 JS 对象的合并等多个用处,其中一个用处就是能够进行浅拷贝。该办法的第一个参数是拷贝的指标对象,前面的参数是拷贝的起源对象(也能够是多个起源)。

object.assign 的语法为:Object.assign(target, ...sources)

object.assign 的示例代码如下:

let target = {};
let source = {a: { b: 1} };
Object.assign(target, source);
console.log(target); // {a: { b: 1} };

然而应用 object.assign 办法有几点须要留神

  • 它不会拷贝对象的继承属性;
  • 它不会拷贝对象的不可枚举的属性;
  • 能够拷贝 Symbol 类型的属性。
let obj1 = {a:{ b:1}, sym:Symbol(1)}; 
Object.defineProperty(obj1, 'innumerable' ,{
    value:'不可枚举属性',
    enumerable:false
});
let obj2 = {};
Object.assign(obj2,obj1)
obj1.a.b = 2;
console.log('obj1',obj1);
console.log('obj2',obj2);

从下面的样例代码中能够看到,利用 object.assign 也能够拷贝 Symbol 类型的对象,然而如果到了对象的第二层属性 obj1.a.b 这里的时候,前者值的扭转也会影响后者的第二层属性的值,阐明其中 仍旧存在着拜访独特堆内存的问题 ,也就是说 这种办法还不能进一步复制,而只是实现了浅拷贝的性能

办法二:扩大运算符形式

  • 咱们也能够利用 JS 的扩大运算符,在结构对象的同时实现浅拷贝的性能。
  • 扩大运算符的语法为:let cloneObj = {...obj};
/* 对象的拷贝 */
let obj = {a:1,b:{c:1}}
let obj2 = {...obj}
obj.a = 2
console.log(obj)  //{a:2,b:{c:1}} console.log(obj2); //{a:1,b:{c:1}}
obj.b.c = 2
console.log(obj)  //{a:2,b:{c:2}} console.log(obj2); //{a:1,b:{c:2}}
/* 数组的拷贝 */
let arr = [1, 2, 3];
let newArr = [...arr]; // 跟 arr.slice()是一样的成果

扩大运算符 和 object.assign 有同样的缺点,也就是 实现的浅拷贝的性能差不多 ,然而如果属性都是 根本类型的值,应用扩大运算符进行浅拷贝会更加不便

办法三:concat 拷贝数组

数组的 concat 办法其实也是浅拷贝,所以连贯一个含有援用类型的数组时,须要留神批改原数组中的元素的属性,因为它会影响拷贝之后连贯的数组。不过 concat 只能用于数组的浅拷贝,应用场景比拟局限。代码如下所示。

let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1] = 100;
console.log(arr);  // [1, 2, 3]
console.log(newArr); // [1, 100, 3]

办法四:slice 拷贝数组

slice 办法也比拟有局限性,因为 它仅仅针对数组类型slice 办法会返回一个新的数组对象,这一对象由该办法的前两个参数来决定原数组截取的开始和完结工夫,是不会影响和扭转原始数组的。

slice 的语法为:arr.slice(begin, end);
let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr);  //[1, 2, { val: 1000} ]

从下面的代码中能够看出,这就是 浅拷贝的限度所在了——它只能拷贝一层对象 。如果 存在对象的嵌套,那么浅拷贝将无能为力。因而深拷贝就是为了解决这个问题而生的,它能解决多层对象嵌套问题,彻底实现拷贝

手工实现一个浅拷贝

依据以上对浅拷贝的了解,如果让你本人实现一个浅拷贝,大抵的思路分为两点:

  • 对根底类型做一个最根本的一个拷贝;
  • 对援用类型开拓一个新的存储,并且拷贝一层对象属性。
const shallowClone = (target) => {if (typeof target === 'object' && target !== null) {const cloneTarget = Array.isArray(target) ? []: {};
    for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = target[prop];
      }
    }
    return cloneTarget;
  } else {return target;}
}

利用类型判断,针对援用类型的对象进行 for 循环遍历对象属性赋值给指标对象的属性,根本就能够手工实现一个浅拷贝的代码了

2. 深拷贝的原理和实现

浅拷贝只是创立了一个新的对象,复制了原有对象的根本类型的值,而援用数据类型只拷贝了一层属性,再深层的还是无奈进行拷贝。深拷贝则不同,对于简单援用数据类型,其在堆内存中齐全开拓了一块内存地址,并将原有的对象齐全复制过去寄存。

这两个对象是互相独立、不受影响的,彻底实现了内存上的拆散。总的来说,深拷贝的原理能够总结如下

将一个对象从内存中残缺地拷贝进去一份给指标对象,并从堆内存中开拓一个全新的空间寄存新对象,且新对象的批改并不会扭转原对象,二者实现真正的拆散。

办法一:乞丐版(JSON.stringify)

JSON.stringify() 是目前开发过程中最简略的深拷贝办法,其实就是把一个对象序列化成为 JSON 的字符串,并将对象外面的内容转换成字符串,最初再用 JSON.parse() 的办法将 JSON 字符串生成一个新的对象

let a = {
    age: 1,
    jobs: {first: 'FE'}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

然而该办法也是有局限性的

  • 会疏忽 undefined
  • 会疏忽 symbol
  • 不能序列化函数
  • 无奈拷贝不可枚举的属性
  • 无奈拷贝对象的原型链
  • 拷贝 RegExp 援用类型会变成空对象
  • 拷贝 Date 援用类型会变成字符串
  • 对象中含有 NaNInfinity 以及 -InfinityJSON 序列化的后果会变成 null
  • 不能解决循环援用的对象,即对象成环 (obj[key] = obj)。
function Obj() {this.func = function () {alert(1) }; 
  this.obj = {a:1};
  this.arr = [1,2,3];
  this.und = undefined; 
  this.reg = /123/; 
  this.date = new Date(0); 
  this.NaN = NaN;
  this.infinity = Infinity;
  this.sym = Symbol(1);
} 
let obj1 = new Obj();
Object.defineProperty(obj1,'innumerable',{ 
  enumerable:false,
  value:'innumerable'
});
console.log('obj1',obj1);
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
console.log('obj2',obj2);

应用 JSON.stringify 办法实现深拷贝对象,尽管到目前为止还有很多无奈实现的性能,然而这种办法足以满足日常的开发需要,并且是最简略和快捷的。而对于其余的也要实现深拷贝的,比拟麻烦的属性对应的数据类型,JSON.stringify 临时还是无奈满足的,那么就须要上面的几种办法了

办法二:根底版(手写递归实现)

上面是一个实现 deepClone 函数封装的例子,通过 for in 遍历传入参数的属性值,如果值是援用类型则再次递归调用该函数,如果是根底数据类型就间接复制

let obj1 = {
  a:{b:1}
}
function deepClone(obj) {let cloneObj = {}
  for(let key in obj) {                 // 遍历
    if(typeof obj[key] ==='object') {cloneObj[key] = deepClone(obj[key])  // 是对象就再次调用该函数递归
    } else {cloneObj[key] = obj[key]  // 根本类型的话间接复制值
    }
  }
  return cloneObj
}
let obj2 = deepClone(obj1);
obj1.a.b = 2;
console.log(obj2);   //  {a:{b:1}}

尽管利用递归能实现一个深拷贝,然而同下面的 JSON.stringify 一样,还是有一些问题没有齐全解决,例如:

  • 这个深拷贝函数并不能复制不可枚举的属性以及 Symbol 类型;
  • 这种办法 只是针对一般的援用类型的值做递归复制,而对于 Array、Date、RegExp、Error、Function 这样的援用类型并不能正确地拷贝;
  • 对象的属性外面成环,即 循环援用没有解决

这种根底版本的写法也比较简单,能够应答大部分的利用状况。然而你在面试的过程中,如果只能写出这样的一个有缺点的深拷贝办法,有可能不会通过。

所以为了“援救”这些缺点,上面我带你一起看看改良的版本,以便于你能够在面试种呈现出更好的深拷贝办法,博得面试官的青眼。

办法三:改进版(改良后递归实现)

针对下面几个待解决问题,我先通过四点相干的实践通知你别离应该怎么做。

  • 针对可能遍历对象的不可枚举属性以及 Symbol 类型,咱们能够应用 Reflect.ownKeys 办法;
  • 当参数为 Date、RegExp 类型,则间接生成一个新的实例返回;
  • 利用 ObjectgetOwnPropertyDescriptors 办法能够取得对象的所有属性,以及对应的个性,顺便联合 Object.create 办法创立一个新对象,并继承传入原对象的原型链;
  • 利用 WeakMap 类型作为 Hash 表,因为 WeakMap 是弱援用类型,能够无效避免内存透露(你能够关注一下 MapweakMap 的要害区别,这里要用 weakMap),作为检测循环援用很有帮忙,如果存在循环,则援用间接返回 WeakMap 存储的值

如果你在思考到循环援用的问题之后,还能用 WeakMap 来很好地解决,并且向面试官解释这样做的目标,那么你所展现的代码,以及你对问题思考的全面性,在面试官眼中应该算是合格的了

实现深拷贝

const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)

const deepClone = function (obj, hash = new WeakMap()) {if (obj.constructor === Date) {return new Date(obj)       // 日期对象间接返回一个新的日期对象
  }

  if (obj.constructor === RegExp){return new RegExp(obj)     // 正则对象间接返回一个新的正则对象
  }

  // 如果循环援用了就用 weakMap 来解决
  if (hash.has(obj)) {return hash.get(obj)
  }
  let allDesc = Object.getOwnPropertyDescriptors(obj)

  // 遍历传入参数所有键的个性
  let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)

  // 把 cloneObj 原型复制到 obj 上
  hash.set(obj, cloneObj)

  for (let key of Reflect.ownKeys(obj)) {cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
  }
  return cloneObj
}
// 上面是验证代码
let obj = {
  num: 0,
  str: '',
  boolean: true,
  unf: undefined,
  nul: null,
  obj: {name: '我是一个对象', id: 1},
  arr: [0, 1, 2],
  func: function () { console.log('我是一个函数') },
  date: new Date(0),
  reg: new RegExp('/ 我是一个正则 /ig'),
  [Symbol('1')]: 1,
};
Object.defineProperty(obj, 'innumerable', {enumerable: false, value: '不可枚举属性'}
);
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj    // 设置 loop 成循环援用的属性
let cloneObj = deepClone(obj)
cloneObj.arr.push(4)
console.log('obj', obj)
console.log('cloneObj', cloneObj)

咱们看一下后果,cloneObjobj 的根底上进行了一次深拷贝,cloneObj 里的 arr 数组进行了批改,并未影响到 obj.arr 的变动,如下图所示

TCP 粘包是怎么回事,如何解决?

默认状况下, TCP 连贯会启⽤提早传送算法 (Nagle 算法), 在数据发送之前缓存他们. 如果短时间有多个数据发送, 会缓冲到⼀起作⼀次发送 (缓冲⼤⼩⻅ socket.bufferSize), 这样能够缩小 IO 耗费提⾼性能.

如果是传输⽂件的话, 那么基本不⽤解决粘包的问题, 来⼀个包拼⼀个包就好了。然而如果是多条音讯, 或者是别的⽤途的数据那么就须要解决粘包.

上面看⼀个例⼦, 间断调⽤两次 send 别离发送两段数据 data1 和 data2, 在接收端有以下⼏种常⻅的状况:
A. 先接管到 data1, 而后接管到 data2 .
B. 先接管到 data1 的局部数据, 而后接管到 data1 余下的局部以及 data2 的全副.
C. 先接管到了 data1 的全副数据和 data2 的局部数据, 而后接管到了 data2 的余下的数据.
D. ⼀次性接管到了 data1 和 data2 的全副数据.

其中的 BCD 就是咱们常⻅的粘包的状况. ⽽对于解决粘包的问题, 常⻅的解决⽅案有:

  • 屡次发送之前距离⼀个等待时间:只须要等上⼀段时间再进⾏下⼀次 send 就好, 适⽤于交互频率特地低的场景. 毛病也很显著, 对于⽐较频繁的场景⽽⾔传输效率切实太低,不过⼏乎不⽤做什么解决.
  • 敞开 Nagle 算法:敞开 Nagle 算法, 在 Node.js 中你能够通过 socket.setNoDelay() ⽅法来敞开 Nagle 算法, 让每⼀次 send 都不缓冲间接发送。该⽅法⽐较适⽤于每次发送的数据都⽐较⼤ (但不是⽂件那么⼤), 并且频率不是特地⾼的场景。如果是每次发送的数据量⽐较⼩, 并且频率特地⾼的, 敞开 Nagle 纯属⾃废文治。另外, 该⽅法不适⽤于⽹络较差的状况, 因为 Nagle 算法是在服务端进⾏的包合并状况, 然而如果短时间内客户端的⽹络状况不好, 或者应⽤层因为某些起因不能及时将 TCP 的数据 recv, 就会造成多个包在客户端缓冲从⽽粘包的状况。(如果是在稳固的机房外部通信那么这个概率是⽐较⼩能够抉择疏忽的)
  • 进⾏封包 / 拆包: 封包 / 拆包是⽬前业内常⻅的解决⽅案了。即给每个数据包在发送之前, 于其前 / 后放⼀些有特色的数据, 而后收到数据的时 候依据特色数据宰割进去各个数据包。

Proxy 代理

proxy 在指标对象的外层搭建了一层拦挡,外界对指标对象的某些操作,必须通过这层拦挡

var proxy = new Proxy(target, handler);

new Proxy()示意生成一个 Proxy 实例,target参数示意所要拦挡的指标对象,handler参数也是一个对象,用来定制拦挡行为

var target = {name: 'poetries'};
 var logHandler = {get: function(target, key) {console.log(`${key} 被读取 `);
     return target[key];
   },
   set: function(target, key, value) {console.log(`${key} 被设置为 ${value}`);
     target[key] = value;
   }
 }
 var targetWithLog = new Proxy(target, logHandler);

 targetWithLog.name; // 控制台输入:name 被读取
 targetWithLog.name = 'others'; // 控制台输入:name 被设置为 others

 console.log(target.name); // 控制台输入: others
  • targetWithLog 读取属性的值时,实际上执行的是 logHandler.get:在控制台输入信息,并且读取被代理对象 target 的属性。
  • targetWithLog 设置属性值时,实际上执行的是 logHandler.set:在控制台输入信息,并且设置被代理对象 target 的属性的值
// 因为拦挡函数总是返回 35,所以拜访任何属性都失去 35
var proxy = new Proxy({}, {get: function(target, property) {return 35;}
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

Proxy 实例也能够作为其余对象的原型对象

var proxy = new Proxy({}, {get: function(target, property) {return 35;}
});

let obj = Object.create(proxy);
obj.time // 35

proxy对象是 obj 对象的原型,obj对象自身并没有 time 属性,所以依据原型链,会在 proxy 对象上读取该属性,导致被拦挡

Proxy 的作用

对于代理模式 Proxy 的作用次要体现在三个方面

  • 拦挡和监督内部对对象的拜访
  • 升高函数或类的复杂度
  • 在简单操作前对操作进行校验或对所需资源进行治理

Proxy 所能代理的范畴 –handler

实际上 handler 自身就是 ES6 所新设计的一个对象. 它的作用就是用来 自定义代理对象的各种可代理操作。它自身一共有 13 中办法, 每种办法都能够代理一种操作. 其 13 种办法如下

// 在读取代理对象的原型时触发该操作,比方在执行 Object.getPrototypeOf(proxy) 时。handler.getPrototypeOf()

// 在设置代理对象的原型时触发该操作,比方在执行 Object.setPrototypeOf(proxy, null) 时。handler.setPrototypeOf()


// 在判断一个代理对象是否是可扩大时触发该操作,比方在执行 Object.isExtensible(proxy) 时。handler.isExtensible()


// 在让一个代理对象不可扩大时触发该操作,比方在执行 Object.preventExtensions(proxy) 时。handler.preventExtensions()

// 在获取代理对象某个属性的属性形容时触发该操作,比方在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时。handler.getOwnPropertyDescriptor()


// 在定义代理对象某个属性时的属性形容时触发该操作,比方在执行 Object.defineProperty(proxy, "foo", {}) 时。andler.defineProperty()


// 在判断代理对象是否领有某个属性时触发该操作,比方在执行 "foo" in proxy 时。handler.has()

// 在读取代理对象的某个属性时触发该操作,比方在执行 proxy.foo 时。handler.get()


// 在给代理对象的某个属性赋值时触发该操作,比方在执行 proxy.foo = 1 时。handler.set()

// 在删除代理对象的某个属性时触发该操作,比方在执行 delete proxy.foo 时。handler.deleteProperty()

// 在获取代理对象的所有属性键时触发该操作,比方在执行 Object.getOwnPropertyNames(proxy) 时。handler.ownKeys()

// 在调用一个指标对象为函数的代理对象时触发该操作,比方在执行 proxy() 时。handler.apply()


// 在给一个指标对象为构造函数的代理对象结构实例时触发该操作,比方在执行 new proxy() 时。handler.construct()

为何 Proxy 不能被 Polyfill

  • 如 class 能够用 function 模仿;promise能够用 callback 模仿
  • 然而 proxy 不能用 Object.defineProperty 模仿

目前谷歌的 polyfill 只能实现局部的性能,如 get、set https://github.com/GoogleChro…

// commonJS require
const proxyPolyfill = require('proxy-polyfill/src/proxy')();

// Your environment may also support transparent rewriting of commonJS to ES6:
import ProxyPolyfillBuilder from 'proxy-polyfill/src/proxy';
const proxyPolyfill = ProxyPolyfillBuilder();

// Then use...
const myProxy = new proxyPolyfill(...);

HTTP 状态码

状态码的类别:

类别 起因 形容
1xx Informational(信息性状态码) 承受的申请正在解决
2xx Success(胜利状态码) 申请失常处理完毕
3xx Redirection(重定向状态码) 须要进行附加操作一实现申请
4xx Client Error (客户端谬误状态码) 服务器无奈解决申请
5xx Server Error(服务器谬误状态码) 服务器解决申请出错

1. 2XX (Success 胜利状态码)

状态码 2XX 示意申请被失常解决了。

(1)200 OK

200 OK 示意客户端发来的申请被服务器端失常解决了。

(2)204 No Content

该状态码示意客户端发送的申请曾经在服务器端失常解决了,然而没有返回的内容,响应报文中不蕴含实体的主体局部。个别在只须要从客户端往服务器端发送信息,而服务器端不须要往客户端发送内容时应用。

(3)206 Partial Content

该状态码示意客户端进行了范畴申请,而服务器端执行了这部分的 GET 申请。响应报文中蕴含由 Content-Range 指定范畴的实体内容。

2. 3XX (Redirection 重定向状态码)

3XX 响应结果表明浏览器须要执行某些非凡的解决以正确处理申请。

(1)301 Moved Permanently

永恒重定向。 该状态码示意申请的资源曾经被调配了新的 URI,当前应应用资源指定的 URI。新的 URI 会在 HTTP 响应头中的 Location 首部字段指定。若用户曾经把原来的 URI 保留为书签,此时会依照 Location 中新的 URI 从新保留该书签。同时,搜索引擎在抓取新内容的同时也将旧的网址替换为重定向之后的网址。

应用场景:

  • 当咱们想换个域名,旧的域名不再应用时,用户拜访旧域名时用 301 就重定向到新的域名。其实也是通知搜索引擎收录的域名须要对新的域名进行收录。
  • 在搜索引擎的搜寻后果中呈现了不带 www 的域名,而带 www 的域名却没有收录,这个时候能够用 301 重定向来通知搜索引擎咱们指标的域名是哪一个。
(2)302 Found

长期重定向。 该状态码示意申请的资源被调配到了新的 URI,心愿用户(本次)能应用新的 URI 拜访资源。和 301 Moved Permanently 状态码类似,然而 302 代表的资源不是被永恒重定向,只是长期性质的。也就是说已挪动的资源对应的 URI 未来还有可能产生扭转。若用户把 URI 保留成书签,但不会像 301 状态码呈现时那样去更新书签,而是仍旧保留返回 302 状态码的页面对应的 URI。同时,搜索引擎会抓取新的内容而保留旧的网址。因为服务器返回 302 代码,搜索引擎认为新的网址只是临时的。

应用场景:

  • 当咱们在做流动时,登录到首页主动重定向,进入流动页面。
  • 未登陆的用户拜访用户核心重定向到登录页面。
  • 拜访 404 页面从新定向到首页。
(3)303 See Other

该状态码示意因为申请对应的资源存在着另一个 URI,应应用 GET 办法定向获取申请的资源。
303 状态码和 302 Found 状态码有着类似的性能,然而 303 状态码明确示意客户端该当采纳 GET 办法获取资源。

303 状态码通常作为 PUT 或 POST 操作的返回后果,它示意重定向链接指向的不是新上传的资源,而是另外一个页面,比方音讯确认页面或上传进度页面。而申请重定向页面的办法要总是应用 GET。

留神:

  • 当 301、302、303 响应状态码返回时,简直所有的浏览器都会把 POST 改成 GET,并删除申请报文内的主体,之后申请会再次主动发送。
  • 301、302 规范是禁止将 POST 办法变成 GET 办法的,但理论大家都会这么做。
(4)304 Not Modified

浏览器缓存相干。 该状态码示意客户端发送附带条件的申请时,服务器端容许申请拜访资源,但未满足条件的状况。304 状态码返回时,不蕴含任何响应的主体局部。304 尽管被划分在 3XX 类别中,然而和重定向没有关系。

带条件的申请(Http 条件申请):应用 Get 办法 申请,申请报文中蕴含(if-matchif-none-matchif-modified-sinceif-unmodified-sinceif-range)中任意首部。

状态码 304 并不是一种谬误,而是通知客户端有缓存,间接应用缓存中的数据。返回页面的只有头部信息,是没有内容局部的,这样在肯定水平上进步了网页的性能。

(5)307 Temporary Redirect

307 示意长期重定向。 该状态码与 302 Found 有着雷同含意,只管 302 规范禁止 POST 变成 GET,然而理论应用时还是这样做了。

307 会恪守浏览器规范,不会从 POST 变成 GET。然而对于解决申请的行为时,不同浏览器还是会呈现不同的状况。标准要求浏览器持续向 Location 的地址 POST 内容。标准要求浏览器持续向 Location 的地址 POST 内容。

3. 4XX (Client Error 客户端谬误状态码)

4XX 的响应结果表明客户端是产生谬误的起因所在。

(1)400 Bad Request

该状态码示意申请报文中存在语法错误。当谬误产生时,需批改申请的内容后再次发送申请。另外,浏览器会像 200 OK 一样看待该状态码。

(2)401 Unauthorized

该状态码示意发送的申请须要有通过 HTTP 认证 (BASIC 认证、DIGEST 认证) 的认证信息。若之前已进行过一次申请,则示意用户认证失败

返回含有 401 的响应必须蕴含一个实用于被申请资源的 WWW-Authenticate 首部用以质询 (challenge) 用户信息。当浏览器首次接管到 401 响应,会弹出认证用的对话窗口。

以下状况会呈现 401:

  • 401.1 – 登录失败。
  • 401.2 – 服务器配置导致登录失败。
  • 401.3 – 因为 ACL 对资源的限度而未取得受权。
  • 401.4 – 筛选器受权失败。
  • 401.5 – ISAPI/CGI 应用程序受权失败。
  • 401.7 – 拜访被 Web 服务器上的 URL 受权策略回绝。这个错误代码为 IIS 6.0 所专用。
(3)403 Forbidden

该状态码表明申请资源的拜访被服务器回绝了,服务器端没有必要给出具体理由,然而能够在响应报文实体的主体中进行阐明。进入该状态后,不能再持续进行验证。该拜访是永恒禁止的,并且与应用逻辑密切相关。

IIS 定义了许多不同的 403 谬误,它们指明更为具体的谬误起因:

  • 403.1 – 执行拜访被禁止。
  • 403.2 – 读拜访被禁止。
  • 403.3 – 写访问被禁止。
  • 403.4 – 要求 SSL。
  • 403.5 – 要求 SSL 128。
  • 403.6 – IP 地址被回绝。
  • 403.7 – 要求客户端证书。
  • 403.8 – 站点拜访被回绝。
  • 403.9 – 用户数过多。
  • 403.10 – 配置有效。
  • 403.11 – 明码更改。
  • 403.12 – 回绝拜访映射表。
  • 403.13 – 客户端证书被撤消。
  • 403.14 – 回绝目录列表。
  • 403.15 – 超出客户端拜访许可。
  • 403.16 – 客户端证书不受信赖或有效。
  • 403.17 – 客户端证书已过期或尚未失效
  • 403.18 – 在以后的应用程序池中不能执行所申请的 URL。这个错误代码为 IIS 6.0 所专用。
  • 403.19 – 不能为这个应用程序池中的客户端执行 CGI。这个错误代码为 IIS 6.0 所专用。
  • 403.20 – Passport 登录失败。这个错误代码为 IIS 6.0 所专用。
(4)404 Not Found

该状态码表明服务器上无奈找到申请的资源。除此之外,也能够在服务器端拒绝请求且不想阐明理由时应用。
以下状况会呈现 404:

  • 404.0 -(无)– 没有找到文件或目录。
  • 404.1 – 无奈在所申请的端口上拜访 Web 站点。
  • 404.2 – Web 服务扩大锁定策略阻止本申请。
  • 404.3 – MIME 映射策略阻止本申请。
(5)405 Method Not Allowed

该状态码示意客户端申请的办法尽管能被服务器辨认,然而服务器禁止应用该办法。GET 和 HEAD 办法,服务器应该总是容许客户端进行拜访。客户端能够通过 OPTIONS 办法(预检)来查看服务器容许的拜访办法, 如下

Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE

4. 5XX (Server Error 服务器谬误状态码)

5XX 的响应结果表明服务器自身产生谬误.

(1)500 Internal Server Error

该状态码表明服务器端在执行申请时产生了谬误。也有可能是 Web 利用存在的 bug 或某些长期的故障。

(2)502 Bad Gateway

该状态码表明表演网关或代理角色的服务器,从上游服务器中接管到的响应是有效的。留神,502 谬误通常不是客户端可能修复的,而是须要由途经的 Web 服务器或者代理服务器对其进行修复。以下状况会呈现 502:

  • 502.1 – CGI(通用网关接口)应用程序超时。
  • 502.2 – CGI(通用网关接口)应用程序出错。
(3)503 Service Unavailable

该状态码表明服务器临时处于超负载或正在进行停机保护,当初无奈解决申请。如果当时得悉解除以上情况须要的工夫,最好写入 RetryAfter 首部字段再返回给客户端。

应用场景:

  • 服务器停机保护时,被动用 503 响应申请;
  • nginx 设置限速,超过限速,会返回 503。
(4)504 Gateway Timeout

该状态码示意网关或者代理的服务器无奈在规定的工夫内取得想要的响应。他是 HTTP 1.1 中新退出的。

应用场景:代码执行工夫超时,或者产生了死循环。

5. 总结

(1)2XX 胜利

  • 200 OK,示意从客户端发来的申请在服务器端被正确处理
  • 204 No content,示意申请胜利,但响应报文不含实体的主体局部
  • 205 Reset Content,示意申请胜利,但响应报文不含实体的主体局部,然而与 204 响应不同在于要求申请方重置内容
  • 206 Partial Content,进行范畴申请

(2)3XX 重定向

  • 301 moved permanently,永久性重定向,示意资源已被调配了新的 URL
  • 302 found,临时性重定向,示意资源长期被调配了新的 URL
  • 303 see other,示意资源存在着另一个 URL,应应用 GET 办法获取资源
  • 304 not modified,示意服务器容许拜访资源,但因产生申请未满足条件的状况
  • 307 temporary redirect,长期重定向,和 302 含意相似,然而冀望客户端放弃申请办法不变向新的地址发出请求

(3)4XX 客户端谬误

  • 400 bad request,申请报文存在语法错误
  • 401 unauthorized,示意发送的申请须要有通过 HTTP 认证的认证信息
  • 403 forbidden,示意对申请资源的拜访被服务器回绝
  • 404 not found,示意在服务器上没有找到申请的资源

(4)5XX 服务器谬误

  • 500 internal sever error,示意服务器端在执行申请时产生了谬误
  • 501 Not Implemented,示意服务器不反对以后申请所须要的某个性能
  • 503 service unavailable,表明服务器临时处于超负载或正在停机保护,无奈解决申请

执行上下文

当执行 JS 代码时,会产生三种执行上下文

  • 全局执行上下文
  • 函数执行上下文
  • eval 执行上下文

每个执行上下文中都有三个重要的属性

  • 变量对象(VO),蕴含变量、函数申明和函数的形参,该属性只能在全局上下文中拜访
  • 作用域链(JS 采纳词法作用域,也就是说变量的作用域是在定义时就决定了)
  • this
var a = 10
function foo(i) {var b = 20}
foo()

对于上述代码,执行栈中有两个上下文:全局上下文和函数 foo 上下文。

stack = [
    globalContext,
    fooContext
]

对于全局上下文来说,VO大略是这样的

globalContext.VO === globe
globalContext.VO = {
    a: undefined,
    foo: <Function>,
}

对于函数 foo 来说,VO 不能拜访,只能拜访到流动对象(AO

fooContext.VO === foo.AO
fooContext.AO {
    i: undefined,
    b: undefined,
    arguments: <>
}
// arguments 是函数独有的对象(箭头函数没有)
// 该对象是一个伪数组,有 `length` 属性且能够通过下标拜访元素
// 该对象中的 `callee` 属性代表函数自身
// `caller` 属性代表函数的调用者

对于作用域链,能够把它了解成蕴含本身变量对象和下级变量对象的列表,通过 [[Scope]]属性查找下级变量

fooContext.[[Scope]] = [globalContext.VO]
fooContext.Scope = fooContext.[[Scope]] + fooContext.VO
fooContext.Scope = [
    fooContext.VO,
    globalContext.VO
]

接下来让咱们看一个陈词滥调的例子,var

b() // call b
console.log(a) // undefined

var a = 'Hello world'

function b() {console.log('call b')
}

想必以上的输入大家必定都曾经明确了,这是因为函数和变量晋升的起因。通常晋升的解释是说将申明的代码移动到了顶部,这其实没有什么谬误,便于大家了解。然而更精确的解释应该是:在生成执行上下文时,会有两个阶段。第一个阶段是创立的阶段(具体步骤是创立 VO),JS 解释器会找出须要晋升的变量和函数,并且给他们提前在内存中开拓好空间,函数的话会将整个函数存入内存中,变量只申明并且赋值为 undefined,所以在第二个阶段,也就是代码执行阶段,咱们能够间接提前应用。

  • 在晋升的过程中,雷同的函数会笼罩上一个函数,并且函数优先于变量晋升
b() // call b second

function b() {console.log('call b fist')
}
function b() {console.log('call b second')
}
var b = 'Hello world'

var会产生很多谬误,所以在 ES6中引入了 letlet不能在申明前应用,然而这并不是常说的 let 不会晋升,let 晋升了申明但没有赋值,因为长期死区导致了并不能在申明前应用。

  • 对于非匿名的立刻执行函数须要留神以下一点
var foo = 1
(function foo() {
    foo = 10
    console.log(foo)
}()) // -> ƒ foo() { foo = 10 ; console.log(foo) }

因为当 JS 解释器在遇到非匿名的立刻执行函数时,会创立一个辅助的特定对象,而后将函数名称作为这个对象的属性,因而函数外部才能够拜访到 foo,然而这个值又是只读的,所以对它的赋值并不失效,所以打印的后果还是这个函数,并且内部的值也没有产生更改。

specialObject = {};

Scope = specialObject + Scope;

foo = new FunctionExpression;
foo.[[Scope]] = Scope;
specialObject.foo = foo; // {DontDelete}, {ReadOnly}

delete Scope[0]; // remove specialObject from the front of scope chain

总结

执行上下文能够简略了解为一个对象:

它蕴含三个局部:

  • 变量对象(VO)
  • 作用域链(词法作用域)
  • this指向

它的类型:

  • 全局执行上下文
  • 函数执行上下文
  • eval执行上下文

代码执行过程:

  • 创立 全局上下文 (global EC)
  • 全局执行上下文 (caller) 逐行 自上而下 执行。遇到函数时,函数执行上下文 (callee) 被 push 到执行栈顶层
  • 函数执行上下文被激活,成为 active EC, 开始执行函数中的代码,caller 被挂起
  • 函数执行完后,calleepop 移除出执行栈,控制权交还全局上下文 (caller),继续执行

介绍一下 Tree Shaking

对 tree-shaking 的理解

作用:

它示意在打包的时候会去除一些无用的代码

原理

  • ES6的模块引入是动态剖析的,所以在编译时能正确判断到底加载了哪些模块
  • 分析程序流,判断哪些变量未被应用、援用,进而删除此代码

特点:

  • 在生产模式下它是默认开启的,然而因为通过 babel 编译全副模块被封装成 IIFE,它存在副作用无奈被tree-shaking
  • 能够在 package.json 中配置 sideEffects 来指定哪些文件是有副作用的。它有两种值,一个是布尔类型,如果是 false 则示意所有文件都没有副作用;如果是一个数组的话,数组里的文件门路示意改文件有副作用
  • rollupwebpack 中对 tree-shaking 的层度不同,例如对 babel 转译后的 class,如果babel 的转译是宽松模式下的话 (也就是loosetrue),webpack依旧会认为它有副作用不会 tree-shaking 掉,而 rollup 会。这是因为 rollup 有程序流剖析的性能,能够更好的判断代码是否真正会产生副作用。

原理

  • ES6 Module 引入进行动态剖析,故而编译的时候正确判断到底加载了那些模块
  • 动态分析程序流,判断那些模块和变量未被应用或者援用,进而删除对应代码

依赖于import/export

通过导入所有的包后再进行条件获取。如下:

import foo from "foo";
import bar from "bar";

if(condition) {// foo.xxxx} else {// bar.xxx}

ES6 的 import 语法完满能够应用 tree shaking,因为能够在代码不运行的状况下就能剖析出不须要的代码

CommonJS 的动静个性模块意味着 tree shaking 不实用。因为它是不可能确定哪些模块理论运行之前是须要的或者是不须要的。在 ES6 中,进入了齐全动态的导入语法:import。这也意味着上面的导入是不可行的:

// 不可行,ES6 的 import 是齐全动态的
if(condition) {myDynamicModule = require("foo");
} else {myDynamicModule = require("bar");
}

模块化

js 中当初比拟成熟的有四种模块加载计划:

  • 第一种是 CommonJS 计划,它通过 require 来引入模块,通过 module.exports 定义模块的输入接口。这种模块加载计划是服务器端的解决方案,它是以同步的形式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取十分快,所以以同步的形式加载没有问题。但如果是在浏览器端,因为模块的加载是应用网络申请,因而应用异步加载的形式更加适合。
  • 第二种是 AMD 计划,这种计划采纳异步加载的形式来加载模块,模块的加载不影响前面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载实现后再执行回调函数。require.js 实现了 AMD 标准
  • 第三种是 CMD 计划,这种计划和 AMD 计划都是为了解决异步模块加载的问题,sea.js 实现了 CMD 标准。它和 require.js 的区别在于模块定义时对依赖的解决不同和对依赖模块的执行机会的解决不同。
  • 第四种计划是 ES6 提出的计划,应用 import 和 export 的模式来导入导出模块

在有 Babel 的状况下,咱们能够间接应用 ES6的模块化

// file a.js
export function a() {}
export function b() {}
// file b.js
export default function() {}

import {a, b} from './a.js'
import XXX from './b.js'

CommonJS

CommonJsNode 独有的标准,浏览器中应用就须要用到 Browserify解析了。

// a.js
module.exports = {a: 1}
// or
exports.a = 1

// b.js
var module = require('./a.js')
module.a // -> log 1

在上述代码中,module.exportsexports 很容易混同,让咱们来看看大抵外部实现

var module = require('./a.js')
module.a
// 这里其实就是包装了一层立刻执行函数,这样就不会净化全局变量了,// 重要的是 module 这里,module 是 Node 独有的一个变量
module.exports = {a: 1}
// 根本实现
var module = {exports: {} // exports 就是个空对象
}
// 这个是为什么 exports 和 module.exports 用法类似的起因
var exports = module.exports
var load = function (module) {
    // 导出的货色
    var a = 1
    module.exports = a
    return module.exports
};

再来说说 module.exportsexports,用法其实是类似的,然而不能对 exports 间接赋值,不会有任何成果。

对于 CommonJSES6 中的模块化的两者区别是:

  • 前者反对动静导入,也就是 require(${path}/xx.js),后者目前不反对,然而已有提案, 前者是同步导入,因为用于服务端,文件都在本地,同步导入即便卡住主线程影响也不大。
  • 而后者是异步导入,因为用于浏览器,须要下载文件,如果也采纳同步导入会对渲染有很大影响
  • 前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会扭转,所以如果想更新值,必须从新导入一次。
  • 然而后者采纳实时绑定的形式,导入导出的值都指向同一个内存地址,所以导入值会追随导出值变动
  • 后者会编译成 require/exports 来执行的

AMD

AMD 是由 RequireJS 提出的

AMD 和 CMD 标准的区别?

  • 第一个方面是在模块定义时对依赖的解决不同。AMD 推崇依赖前置,在定义模块的时候就要申明其依赖的模块。而 CMD 推崇就近依赖,只有在用到某个模块的时候再去 require。
  • 第二个方面是对依赖模块的执行机会解决不同。首先 AMD 和 CMD 对于模块的加载形式都是异步加载,不过它们的区别在于模块的执行机会,AMD 在依赖模块加载实现后就间接执行依赖模块,依赖模块的执行程序和咱们书写的程序不肯定统一。而 CMD 在依赖模块加载实现后并不执行,只是下载而已,等到所有的依赖模块都加载好后,进入回调函数逻辑,遇到 require 语句的时候才执行对应的模块,这样模块的执行程序就和咱们书写的程序保持一致了。
// CMD
define(function(require, exports, module) {var a = require("./a");
  a.doSomething();
  // 此处略去 100 行
  var b = require("./b"); // 依赖能够就近书写
  b.doSomething();
  // ...
});

// AMD 默认举荐
define(["./a", "./b"], function(a, b) {
  // 依赖必须一开始就写好
  a.doSomething();
  // 此处略去 100 行
  b.doSomething();
  // ...
})
  • AMDrequirejs 在推广过程中对模块定义的规范化产出,提前执行,推崇依赖前置
  • CMDseajs 在推广过程中对模块定义的规范化产出,提早执行,推崇依赖就近
  • CommonJs:模块输入的是一个值的 copy,运行时加载,加载的是一个对象(module.exports 属性),该对象只有在脚本运行完才会生成
  • ES6 Module:模块输入的是一个值的援用,编译时输入接口,ES6模块不是对象,它对外接口只是一种动态定义,在代码动态解析阶段就会生成。

谈谈对模块化开发的了解

  • 我对模块的了解是,一个模块是实现一个特定性能的一组办法。在最开始的时候,js 只实现一些简略的性能,所以并没有模块的概念,但随着程序越来越简单,代码的模块化开发变得越来越重要。
  • 因为函数具备独立作用域的特点,最原始的写法是应用函数来作为模块,几个函数作为一个模块,然而这种形式容易造成全局变量的净化,并且模块间没有分割。
  • 前面提出了对象写法,通过将函数作为一个对象的办法来实现,这样解决了间接应用函数作为模块的一些毛病,然而这种方法会裸露所有的所有的模块成员,内部代码能够批改外部属性的值。
  • 当初最罕用的是立刻执行函数的写法,通过利用闭包来实现模块公有作用域的建设,同时不会对全局作用域造成净化。

盒模型

content(元素内容)+ padding(内边距)+ border(边框)+ margin(外边距)

延长:box-sizing

  • content-box:默认值,总宽度 = margin + border + padding + width
  • border-box:盒子宽度蕴含 paddingborder总宽度 = margin + width
  • inherit:从父元素继承 box-sizing 属性
正文完
 0