前端 进阶

前端 进阶

  • 一、HTML
    • meta
    • viewport
    • [题] meta标签,实现页面自动刷新/跳转
  • 二、CSS
    • CSS选择器
    • CSS选择器匹配原理
    • CSS优先级 / 权重
    • 可继承 / 不可继承属性
    • 盒模型
    • offsetWidth、clientWidth、scrollWidth**
    • box-sizing属性
    • BFC块级格式化上下文
    • position定位
    • 实现水平居中
    • 实现垂直居中
    • 垂直居中一个``
    • 实现一个浮动元素垂直居中?
    • display属性
    • CSS3新特性
    • flex弹性盒布局
    • CSS创建一个三角形
    • 实现一个广告跑马灯效果
    • 实现一个文字颜色跑马灯效果
    • 实现一个简单的幻灯片效果
    • 实现一个手机版淘宝商品并列显示
    • 设计一个满屏品字布局
    • CSS常见的兼容性问题
  • 三、JavaScript
    • JavaScript 执行机制
      • 【题】异步加载JS的方式?
    • 事件循环 Event Loop
      • 【题】异步代码执行顺序?解释一下什么是 Event Loop ?
      • 【题】小试牛刀
    • JS内置数据类型
      • 1、数据类型
      • 2、数据类型存储
      • 【题】小试牛刀
      • 【题】 null 和 undefined 的区别?
      • 3、数据类型检测
      • 【题】实现一个数据类型判断方法
      • 4、数据类型转换
      • 【题】类型转换,经典面试题
    • JS各种运算符
    • 数组
      • 1、创建数组
        • 2、增删查改方法
        • 3、排序方法
        • 4、转换方法
        • 5、迭代方法
        • 6、数组最高/最低数值
        • 7、判断是否数组
        • 8、ES6新扩展
      • 【题】js实现随机选取10–100之间的10个数字,存入一个数组,并排序?
      • 【题】计算数组元素总和?
        • [题] 数组扁平化?
        • [题] 数组去重
    • 对象
        • 对象的创建
    • 字符串
        • 字符串的常用方法
    • 浅拷贝 / 深拷贝
        • 浅拷贝
        • 深拷贝
        • 区别
        • [题] js实现一个clone函数 | 深拷贝?
    • This
    • apply/call/bind
    • 变量提升
    • 执行上下文
    • 作用域
    • 闭包
        • [题] 大厂面试,循环输出问题?
    • new操作符
    • 原型 / 原型链
    • 继承
    • 面向对象
    • 事件机制
        • 捕获 / 冒泡:
        • 事件对象
        • 阻止冒泡 / 捕获
        • 事件模型
        • 事件代理(又叫事件委托)
    • 节流与防抖
    • 模块化
    • Promise
      • [题] 手写实现promise
    • async/await
        • [题] 小试牛刀
  • 浏览器
  • HTTP
  • 前端性能优化
    • 定位问题
      • 技术选型
      • NetWork
    • 优化方案
  • 兼容、适配问题
        • [题] 移动端 1px 问题
  • TypeScript
      • 类型?
      • 接口(interface)
  • Vue2 & Vue3
      • Vue中data为什么要返回函数?
        • Vue3新特性 ?
        • Vue2 & Vue3 响应原理对比?
        • Vue3有了解过吗?能说说跟Vue2的区别吗?
  • 小试牛刀
      • 1. JS中 `??` 与 `||` 的区别?

一、HTML

meta

<!DOCTYPE html> <!--H5标准声明,使用 HTML5 doctype,不区分大小写-->
<head lang=”en”> <!--标准的 lang 属性写法-->
<meta charset=’utf-8′>    <!--声明文档使用的字符编码-->
<meta http-equiv=”X-UA-Compatible” content=”IE=edge,chrome=1″/>   <!--优先使用 IE 最新版本和 Chrome-->
<meta name=”description” content=”不超过150个字符”/>       <!--页面描述-->
<meta name=”keywords” content=””/>     <!-- 页面关键词-->
<meta name=”author” content=”name, email@gmail.com”/>    <!--网页作者-->
<meta name=”robots” content=”index,follow”/>      <!--搜索引擎抓取-->
<meta name=”viewport” content=”initial-scale=1, maximum-scale=3, minimum-scale=1, user-scalable=no”> <!--为移动设备添加 viewport-->
<meta name=”apple-mobile-web-app-title” content=”标题”> <!--iOS 设备 begin-->
<meta name=”apple-mobile-web-app-capable” content=”yes”/>  <!--添加到主屏后的标题(iOS 6 新增)
是否启用 WebApp 全屏模式,删除苹果默认的工具栏和菜单栏-->
<meta name=”apple-itunes-app” content=”app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL”>
<!--添加智能 App 广告条 Smart App Banner(iOS 6+ Safari)-->
<meta name=”apple-mobile-web-app-status-bar-style” content=”black”/>
<meta name=”format-detection” content=”telphone=no, email=no”/>  <!--设置苹果工具栏颜色-->
<meta name=”renderer” content=”webkit”> <!-- 启用360浏览器的极速模式(webkit)-->
<meta http-equiv=”X-UA-Compatible” content=”IE=edge”>     <!--避免IE使用兼容模式-->
<meta http-equiv=”Cache-Control” content=”no-siteapp” />    <!--不让百度转码-->
<meta name=”HandheldFriendly” content=”true”>     <!--针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓-->
<meta name=”MobileOptimized” content=”320″>   <!--微软的老式浏览器-->
<meta name=”screen-orientation” content=”portrait”>   <!--uc强制竖屏-->
<meta name=”x5-orientation” content=”portrait”>    <!--QQ强制竖屏-->
<meta name=”full-screen” content=”yes”>              <!--UC强制全屏-->
<meta name=”x5-fullscreen” content=”true”>       <!--QQ强制全屏-->
<meta name=”browsermode” content=”application”>   <!--UC应用模式-->
<meta name=”x5-page-mode” content=”app”>   <!-- QQ应用模式-->
<meta name=”msapplication-tap-highlight” content=”no”>    <!--windows phone 点击无高亮
设置页面不缓存-->
<meta http-equiv=”pragma” content=”no-cache”>
<meta http-equiv=”cache-control” content=”no-cache”>
<meta http-equiv=”expires” content=”0″>

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 是否允许手动缩放

[题] meta标签,实现页面自动刷新/跳转

实现一个类似 PPT 自动播放的效果

<meta http-equiv="Refresh" content="5; URL=page2.html">
// 5s 之后自动跳转到同域下的 page2.html 页面

另一种场景,比如每隔一分钟就需要刷新页面的大屏幕监控

<meta http-equiv="Refresh" content="60">

二、CSS

CSS选择器

  • id选择器(#box)
  • 类选择器 (.myclassname)
  • 标签选择器 (div)
  • 属性选择器 a[rel=“external”])
  • 后代选择器(li a)
  • 子选择器(ul > li)
  • 相邻选择器(h1 + p)
  • 层次选择器(p~ul),选择前面有p元素的每个ul元素
  • 伪类选择器(a:hover, li:nth-child)
  • 伪元素选择器
  • 群组选择器(h1, p, span)
  • 通配符选择器(*)

伪类选择器

:link :选择未被访问的链接
:visited:选取已被访问的链接
:active:选择活动链接
:hover :鼠标指针浮动在上面的元素
:focus :选择具有焦点的
:first-child:父元素的首个子元素
es6:
:first-of-type 父元素的首个元素
:last-of-type 父元素的最后一个元素
:only-of-type 父元素的特定类型的唯一子元素
:only-child 父元素中唯一子元素
:nth-child(n) 选择父元素中第N个子元素
:nth-last-of-type(n) 选择父元素中第N个子元素,从后往前
:last-child 父元素的最后一个元素
:root 设置HTML文档
:empty 指定空的元素
:enabled 选择被禁用元素
:disabled 选择被禁用元素
:checked 选择选中的元素
:not(selector) 选择非 <selector> 元素的所有元素

伪元素选择器

:first-letter :用于选取指定选择器的首字母
:first-line :选取指定选择器的首行
:before : 选择器在被选元素的内容前面插入内容
:after : 选择器在被选元素的内容后面插入内容

属性选择器

[attribute] 选择带有attribute属性的元素
[attribute=value] 选择所有使用attribute=value的元素
[attribute~=value] 选择attribute属性包含value的元素
[attribute|=value]:选择attribute属性以value开头的元素
es6:
[attribute*=value]:选择attribute属性值包含value的所有元素
[attribute^=value]:选择attribute属性开头为value的所有元素
[attribute$=value]:选择attribute属性结尾为value的所有元素

CSS选择器匹配原理

采取 从右向左 的顺序来匹配选择器。从最具体的选择器开始

CSS优先级 / 权重

三条规则:

  • 权重不同的样式规则作用于同一元素时,权重高的规则生效
  • 权重相同的样式规则作用于同一元素时,后声明的规则生效
  • 直接作用于元素的样式规则优先级高于从祖先元素继承的规则;

CSS 权重等级(优先级:从上往下,由高到低)

  • !important 大于一切 (10000)
  • 内联样式 = 外联样式 (1000)
  • ID选择器 (100)
  • 类选择器 = 伪类选择器 = 属性选择器 (10)
  • 标签选择器 = 伪元素选择器 (1)
  • 通配选择器 = 后代选择器 = 兄弟选择器 (0)
div{ /*1*/ } 
.class1{ /*10*/ }
#id1{ /*100*/ }
#id1 div{ /*100 + 1*/ }

可继承 / 不可继承属性

在css中,继承是指的是给父元素设置一些属性,后代元素会自动拥有这些属性

可继承的样式

1、字体系列属性

font:组合字体
font-family:规定元素的字体系列
font-weight:设置字体的粗细
font-size:设置字体的尺寸
font-style:定义字体的风格
font-variant:偏大或偏小的字体

2、文本系列属性

text-indent:文本缩进
text-align:文本水平
line-height:行高
word-spacing:增加或减少单词间的空白
letter-spacing:增加或减少字符间的空白
text-transform:控制文本大小写
direction:规定文本的书写方向
color:文本颜色

3、元素可见性

visibility: hidden;

4、表格布局属性

caption-side:定位表格标题位置
border-collapse:合并表格边框
border-spacing:设置相邻单元格的边框间的距离
empty-cells:单元格的边框的出现与消失
table-layout:表格的宽度由什么决定

5、列表属性

list-style-type:文字前面的小点点样式
list-style-position:小点点位置
list-style:以上的属性可通过这属性集合

6、引用

quotes:设置嵌套引用的引号类型

7、光标属性

cursor: 箭头可以变成需要的形状

特殊的几点:
a 标签的字体颜色不能被继承
h1-h6标签字体的大下也是不能被继承的

不可继承的样式

  • display
  • 文本属性:vertical-align、text-decoration
  • 盒子模型的属性:宽度、高度、内外边距、边框等
  • 背景属性:背景图片、颜色、位置等
  • 定位属性:浮动、清除浮动、定位position等
  • 生成内容属性:content、counter-reset、counter-increment
  • 轮廓样式属性:outline-style、outline-width、outline-color、outline
  • 页面样式属性:size、page-break-before、page-break-after

盒模型

标准(W3C)盒模型:元素总宽度 = width + padding + border + margin
怪异(IE)盒模型:元素总宽度 = width + margin

.box{width: 200px;height: 200px;padding: 20px;border: 10px solid #000;margin: 15px;box-sizing: content-box;
}
// box宽度 = 200 + 20`*`2 + 10`*`2 = 260px;
.box{width: 200px;;height: 200px;padding: 20px;border: 10px solid #000;margin: 15px;box-sizing: border-box;
}
// box宽度 = 200px;

offsetWidth、clientWidth、scrollWidth**

  • offsetWidth/offsetHeight = content + padding + border
  • clientWidth/clientHeight = content + padding
  • scrollWidth/scrollHeight = content + padding + border + 滚动条

box-sizing属性

默认为content-box

  • box-sizing: content-box; 默认的标准(W3C)盒模型元素效果
  • box-sizing: border-box; 触发怪异(IE)盒模型元素的效果
  • box-sizing: inherit; 继承父元素 box-sizing 属性的值

BFC块级格式化上下文

BFC(Block Formatting Context),块级格式化上下文,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响

触发条件 (以下任意一条)

  • float的值不为none
  • overflow的值不为visible
  • display的值为table-cell、tabble-caption和inline-block之一
  • position的值不为static或则releative中的任何一个
  • IE下, 可通过zoom:1 触发

BFC布局与普通文档流布局区别:
普通文档流布局:

  • 浮动的元素是不会被父级计算高度
  • 非浮动元素会覆盖浮动元素的位置
  • margin会传递给父级元素
  • 两个相邻元素上下的margin会重叠

BFC布局规则:

  • 浮动的元素会被父级计算高度(父级元素触发了BFC)
  • 非浮动元素不会覆盖浮动元素的位置(非浮动元素触发了BFC)
  • margin不会传递给父级(父级触发BFC)
  • 属于同一个BFC的两个相邻元素上下margin会重叠

应用场景:

  • 阻止margin重叠
  • 可以包含浮动元素, 清除内部浮动
  • 自适应两栏布局
  • 可以阻止元素被浮动元素覆盖

position定位

  • static (默认)没有定位,按照正常文档流进行排列;
  • inherit 规定从父元素继承 position 属性的值
  • relative(相对定位),不脱离文档流,参考自身静态位置通过 top, bottom, left, right 定位;
  • absolute(绝对定位),参考距其最近一个不为static的父级元素通过top, bottom, left, right 定位;
  • fixed(固定定位),相对于可视窗口进行定位

实现水平居中

  • 行内元素:设置父元素text-align:center
  • 块级元素:
    1、宽度固定,设置左右margin: auto;或者left:50%margin-left值为宽度一半的负值
    2、宽度不固定,设置position: absolute; left: 50%; transform:translateX(-50%); 或者 设置父元素 flex布局,指定justify-content: center
  • display: table-cell;

实现垂直居中

  • 文本:设置line-heightheight
  • 块级元素
    1、固定高度时设置top:50%margin-top值为高度一半的负值
    2、设置bottom:0,top:0,并设置margin:auto
    3、父元素设置 flex布局,设置为align-item:center
  • 将显示方式设置为表格,display:table-cell,同时设置vertial-align:middle

垂直居中一个<img>

.img-box{display:table-cell;text-align:center;vertical-align:middle;
}

实现一个浮动元素垂直居中?

已知元素的高宽

#div1{width:200px;height:200px;background-color:#6699FF;position: absolute;        //父元素需要相对定位top: 50%;left: 50%;margin-top: -100px ;   //二分之一的height,widthmargin-left: -100px;
}
#div1{width: 200px;height: 200px;background-color: #6699FF;position: absolute;        //父元素需要相对定位left: 0;top: 0;right: 0;bottom: 0;margin:auto;
}

未知元素的高宽

Flex布局
.box {width: 100vw;height: 100vh;display: flex;align-items: center;justify-content: center;
}.box-center{background-color: greenyellow;
}
绝对定位
.box-center{background-color: greenyellow;position: fixed;top: 50%;left: 50%;transfrom: translate(-50%, -50%);
}

display属性

  • block 块状元素
  • inline 行内元素 (默认)
  • inline-block 象行内元素一样显示,但其内容象块类型元素一样显示。
  • inline-flex 象行内元素一样显示,但其内容象弹性元素一样显示
  • list-item 象块类型元素一样显示,并添加样式列表标记。
  • table 块级表格元素
  • inherit 从父元素继承 display 属性的值
  • none 元素不可见。

CSS3新特性

  • 新增选择器
  • 弹性盒模型 display: flex;
  • 多列布局 column-count: 5;
  • 媒体查询 @media (max-width: 480px) {}
  • 个性化字体 @font-face{font-family: BorderWeb; src:url(BORDERW0.eot);}
  • 颜色透明度rgba color: rgba(255, 0, 0, 0.75);
  • 圆角 border-radius: 5px;
  • 渐变 background:linear-gradient(red, green, blue);
  • 阴影 box-shadow:3px 3px 3px rgba(0, 64, 128, 0.3);
  • 倒影 box-reflect: below 2px;
  • 文字装饰 text-stroke-color: red;
  • 文字溢出 text-overflow:ellipsis;
  • 背景效果 background-size: 100px 100px;
  • 边框效果 border-image:url(bt_blue.png) 0 10;
  • 转换
    旋转 transform: rotate(20deg);
    倾斜 transform: skew(150deg, -10deg);
    位移 transform: translate(20px, 20px);
    缩放 transform: scale(.5);
  • 平滑过渡 transition: all .3s ease-in .1s;
  • 动画 @keyframes anim-1 {50% {border-radius: 50%;}} animation: anim-1 1s;

flex弹性盒布局

设置flex弹性盒布局

display: flex;
display: inline-flex;

flex-direction 决定主轴的方向

  • row(默认值):主轴为水平方向,起点在左端。
  • row-reverse:主轴为水平方向,起点在右端。
  • column:主轴为垂直方向,起点在上沿。
  • column-reverse:主轴为垂直方向,起点在下沿。

在这里插入图片描述
justify-content 设置主轴的对齐方式

  • flex-start(默认值):向主轴开始方向对齐。
  • flex-end:向主轴结束方向对齐。
  • center: 居中。
  • space-between:两端对齐,项目之间的间隔都相等。
  • space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。
    在这里插入图片描述
    align-items 设置交叉轴的对齐方式
  • flex-start:交叉轴的起点对齐。
  • flex-end:交叉轴的终点对齐。
  • center:交叉轴的中点对齐。
  • baseline: 项目的第一行文字的基线对齐。
  • stretch(默认值):如果项目未设置高度或设为 auto,将占满整个容器的高度。
    在这里插入图片描述

CSS创建一个三角形

div {width: 0;height: 0;border: 40px solid;border-color: orange blue red green;
}

在这里插入图片描述


/* 
把元素的宽度、高度设为0。
把上、左、右三条边框隐藏掉(颜色设为 transparent)
*/
#demo {width: 0;height: 0;border-width: 20px;border-style: solid;border-color: transparent transparent red transparent;
}

在这里插入图片描述

实现一个广告跑马灯效果

单行文字跑起来:

<div class="box"><p class="text">跑马灯效果,跑起来~~</p>
</div>
.box{width: 400px;border: 1px solid #000;overflow: hidden;
}
.text{font-size: 24px;color: red;line-height: 60px;display: inline-block;width: 100%;animation: 10s loop linear infinite normal;
}
@keyframes loop {0%{transform: translateX(0px);}100% {transform: translateX(100%);}
}

实现一个文字颜色跑马灯效果

<div class="box"><p class="text">文字颜色跑马灯效果,跑起来~~</p>
</div>
.box{width: 400px;border: 1px solid #000;overflow: hidden;
}
.text{font-size: 24px;line-height: 60px;background-image: linear-gradient(90deg,#ffffff,#ff0000 6.25%,#ff7d00 12.5%,#ffff00 18.75%, #00ff00 25%,#ffff00 50%,#ffff00 100%);-webkit-background-clip: text;  color: transparent;  background-size: 200% 100%;animation: 2s loop linear infinite;
}
@keyframes loop {0%{background-position: 0 0;}100% {background-position: -100% 0;}
}

实现一个简单的幻灯片效果

幻灯片,自动切换

.box{width: 200px;height: 200px;overflow: hidden;background-size: cover;background-position: center;animation: 'loops' 20s infinite;
}@-webkit-keyframes "loops" {0% {background:url(./bg1.jpg) no-repeat;             }25% {background:url(./bg2.jpg) no-repeat;}50% {background:url(./bg3.jpg) no-repeat;}75% {background:url(./bg4.jpg) no-repeat;}100% {background:url(./bg5.jpg) no-repeat;}
}

实现一个手机版淘宝商品并列显示

<div class="list"><div class="item"><p class="text">1跑马灯效果,跑起来~~跑马灯效果,跑起来~~</p></div><div class="item"><p class="text">2跑马灯效果,跑起来~~</p></div><div class="item"><p class="text">3跑马灯效果,跑起来~~跑马灯效果,跑起来~~</p></div><div class="item"><p class="text">4跑马灯效果,跑起来~~跑马灯效果,跑起来~~</p></div><div class="item"><p class="text">5跑马灯效果,跑起来~~</p></div>
</div>
.list{width: 400px; /* 设置宽度 */column-count: 2; /* 1~10 列数,自定分配宽度 */column-gap: 1em; /* 设置列与列之前的间距 */margin: 0 auto;
}
.item{padding: 10px;margin-bottom: 1em;border: 1px solid #000;border-radius: 10px;break-inside: avoid;
}

设计一个满屏品字布局

1、
div设置成100%,子元素分别宽50%
上面那块用float或者display: block;+margin: 0 auto;居中;
下面两块用float或者inline-block不换行;

<div class="main"><div class="header"></div><div class="left"></div><div class="right"></div>
</div>  
.main {height: 100%;width: 100%;}
.header {display: block;width: 50%;height: 50%;background-color:  rgb(255,2545,167);margin: 0 auto;
}
.left {display: inline-block; /* float: left; */width: 50%;height: 100%;background-color: rgb(204,255,102);
}
.right {display: inline-block; /* float: left; */width: 50%;height: 100%;background-color: rgb(189,255,255);
}

2、
上面那块用margin: 0 auto;居中;
下面两块用float或者inline-block不换行;

<div class="header"></div>
<div class="main"><div class="left"></div><div class="right"></div>
</div>  
.header {width: 50%;height: 50%;background-color:  rgb(255,2545,167);margin: 0 auto;
}
.main {height: 50%;width: 100%;}
.left {display: inine-block; /* float: left; */width: 50%;height: 100%;background-color: rgb(204,255,102);
}
.right {display: inine-block; /* float: left; */width: 50%;height: 100%;background-color: rgb(189,255,255);
}

在这里插入图片描述

CSS常见的兼容性问题

三、JavaScript

JavaScript 执行机制

最原始的 JavaScript 文件加载方式,就是Script 标签。

同步/ 异步

最原始的 JavaScript 文件加载方式,就是Script 标签。

默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到<script>标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。

如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。这显然是很不好的体验,所以浏览器允许脚本异步加载。

<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>

<script>标签添加defer或async属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。
defer:要等到整个页面在内存中正常渲染结束,才会执行;多个脚本时,按顺序执行
async:一旦下载完,渲染引擎就会中断渲染,执行这个脚本再继续渲染。多个脚本时,不能保证按执行顺序,谁先下载好先执行谁
总结一句话:defer是“渲染完再执行”,async是“下载完就执行”。

【题】异步加载JS的方式?

  • defer:设置<script>属性 defer="defer"
  • async: 设置<script>属性 async="async"
  • 动态创建 script DOM:document.createElement('script');
  • XmlHttpRequest 脚本注入
  • 异步加载库 LABjs
  • 模块加载器 Sea.js

事件循环 Event Loop

在这里插入图片描述
1、默认代码从上到下执行,执行环境通过script来执行(宏任务)
2、在代码执行过程中,调用定时器 promise click事件…不会立即执行,需要等待当前代码全部执行完毕
3、给异步方法划分队列,分别存放到微任务(立即存放)和宏任务(时间到了或事情发生了才存放)到队列中
4、script执行完毕后,会选择清空所有的微任务
5、微任务执行完毕后,会渲染页面(不是每次都调用)
6、再去宏任务队列中看有没有到达时间的,拿出来其中一个执行
7、执行完毕后,按照上述步骤不停的循环

微任务
process.nextTick
promise
Object.observe
MutationObserver
在这里插入图片描述
宏任务
script
setTimeout
setInterval
setImmediate
I/O 网络请求完成、文件读写完成事件(Ajax)
UI rendering
用户交互事件(比如鼠标点击、滚动页面、放大缩小等)

【题】异步代码执行顺序?解释一下什么是 Event Loop ?

简单说法:
执行同步代码,这属于宏任务
执行栈为空,查询是否有微任务需要执行
执行所有微任务
必要的话渲染 UI
然后开始下一轮 Event loop,执行宏任务中的异步代码

  • 首先js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行
  • 在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务
  • 当同步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行
  • 任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行
  • 当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。

【题】小试牛刀

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

setTimeout(function() {console.log(1)
}, 0);
new Promise(function(resolve, reject) {console.log(2);resolve()
}).then(function() {console.log(3)
});
process.nextTick(function () {console.log(4)
})
console.log(5)
// 2, 5, 4, 3, 1
console.log(1)
async function asyncFunc(){console.log(2)// await xx ==> promise.resolve(()=>{console.log(3)}).then()// console.log(3) 放到promise.resolve或立即执行await console.log(3) // 相当于把console.log(4)放到了then promise.resolve(()=>{console.log(3)}).then(()=>{//   console.log(4)// })// 微任务谁先注册谁先执行console.log(4)
}
setTimeout(()=>{console.log(5)})
const promise = new Promise((resolve,reject)=>{console.log(6)resolve(7)
})
promise.then(d=>{console.log(d)})
asyncFunc()
console.log(8)// 输出 1 6 2 3 8 7 4 5

JS内置数据类型

1、数据类型

在这里插入图片描述
7种基本类型:undefined, null, String, Number, Boolean, Symbol, BigInt(es10新增)
引用类型: Object, Array, Function, Date, Math, RegExp

2、数据类型存储

栈内存、堆内存
基本数据类型:存储在栈内存中,占据空间小、大小固定,属于被频繁使用数据,,被拷贝时,会创建一个完全相等的变量;
引用数据类型:指针(地址)存储在栈内存,指针指向堆内存中的数据,占据空间大、大小不固定。多个引用指向同一个地址,存在共享数据的可能。

【题】小试牛刀

let a = {name: 'lee',age: 18
}
let b = a; // “共享”
console.log(a.name);  // lee
b.name = 'son';
console.log(a.name);  // son
console.log(b.name);  // son
let a = {name: 'Julia',age: 20
}
function change(o) {o.age = 24;o = {name: 'Kath',age: 30}return o;
}
let b = change(a);
console.log(b.age);    // 30
console.log(a.age);    // 24
// 函数传参进来的 o,传递的是对象在堆中的内存地址值

【题】 null 和 undefined 的区别?

1、Undefined 和 Null 都是基本数据类型。都只有一个值,就是 undefined 和 null。
2、undefined 代表的含义是未定义, null 代表的含义是空对象
3、我们使用双等号对两种类型的值进行比较时会返回 true,使用全等号时会返回 false。

3、数据类型检测

typeofinstanceofconstructorObject.prototype.toString.call()Array.isArray(arr)

typeof

返回一个类型字符串。类型结果 undefined, number, string, boolean, function, object
缺陷:不能准确判断引用类型

typeof 'str' // string
typeof 2  // number
typeof NaN  // number
typeof true // boolean
typeof undefined // undefined
typeof null // object
typeof function(){} // function
typeof {} // object
typeof [] // object

instanceof

原理:instanceof 内部机制是通过判断对象的原型链中是不是能找到类型的 prototype
返回判断结果 true/false,
缺陷:不能检测基本数据类型

console.log(2 instanceof Number);                    // false
console.log(true instanceof Boolean);                // false 
console.log('str' instanceof String);                // false  
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true    
console.log(undefined instanceof Undefined); 	 	// 报错
console.log(null instanceof Null); 	 			 	// 报错

手动实现一个instanceof

function instanceof(left, right) {// 获得类型的原型let prototype = right.prototype// 获得对象的原型left = left.__proto__// 判断对象的类型是否等于类型的原型while (true) {if (left === null)return falseif (prototype === left)return trueleft = left.__proto__}
}

constructor

返回当前实例所属类信息
缺陷:如果一个对象,更改它的原型,constructor就会变得不可靠了

('str').constructor === String // true
(2).constructor === Number // true
(true).constructor === Boolean// true
(undefined).constructor === Undefined // 报错
(null).constructor === Null// 报错
(function(){}).constructor === Function // true
...

Object.prototype.toString.call()

调用Object 的原型方法toString(),可以统一返回格式为 “[object Xxx]” 的字符串,其中 Xxx 就是对象的类型。对于 Object 以外的其他对象,则需要通过 call 来调用,才能返回正确的类型信息。
返回 “[object Xxx]”的字符串

Object.prototype.toString.call('1')  // "[object String]"
Object.prototype.toString.call(1)    // "[object Number]"
Object.prototype.toString.call(true)  // "[object Boolean]"
Object.prototype.toString.call(null)   //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(function(){})  // "[object Function]"
Object.prototype.toString({})       // "[object Object]"
Object.prototype.toString.call({})  // 同上结果,加上call也ok
Object.prototype.toString.call([])       //"[object Array]"
Object.prototype.toString.call(/123/g)    //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call(document)  //"[object HTMLDocument]"
Object.prototype.toString.call(window)   //"[object Window]"

【题】实现一个数据类型判断方法

function getType(obj){let type  = typeof obj;if (type === "object" && type !== null) {return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');} else {return type;}
}

4、数据类型转换

JS 中类型转换只有三种情况,分别是:
转换为布尔值
转换为数字
转换为字符串

转Boolean

在条件判断时,除了 undefined,null, false, NaN, '', 0, -0,转为 false ,其他所有值都转为 true,包括所有对象

强制转换(显示转换)
自动转换(隐式转换)
Object 的转换规则

对象转原始类型

会调用内置的 [[ToPrimitive]] 函数,其规则逻辑如下:
1、如果已经是原始类型了,那就不需要转换了;
2、调用 x.valueOf(),如果转换为基础类型,则返回;
3、调用 x.toString(),如果转换为基础类型,则返回;
4、如果都没有返回基础类型,会报错。

var obj = {value: 1,valueOf() {return 2;},toString() {return '3'},[Symbol.toPrimitive]() {return 4}
}
console.log(obj + 1); // 输出5

JS 中类型转换:

  • 隐式转换(由四则运算、比较运算引起的)
  • 强制转换(Number(),parseInt(),parseFloat(), toString(),String(),Boolean()

隐式转换

算术运算(+、-、*、/、%)
比较运算(==、!=、>、<)、if、while需要布尔值地方

四则运算(+、-、*、/、%)规则:
1、+ 运算,一旦一方是字符串,就会进行字符串拼接操作
2、除了 +运算 有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值(+运算优先转字符串,其他运算,只要其中一方是数字,那么另一方就会被转为数字)
3、null转数值时,值为0undefined转数值时,值为NaN

'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"'5' - '2' // 3
'5' * '2' // 10
true - 1  // 0
false - 1 // -1
'1' - 1   // 0
'5' * []    // 0
false / '5' // 0
'abc' - 1   // NaN
null + 1 // 1
undefined + 1 // NaN
'a' + + 'b' // -> "aNaN",  + 'b' 等于 NaN

’+’ 的隐式类型转换规则
1、两边都是数字时,进行的是加法运算
2、两边都是字符串,则直接拼接
3、其中一个是字符串、一个是数字,则按照字符串规则进行拼接
4、有一个是字符串,另外一个是 undefined、null 或布尔型,则调用 toString() 方法进行字符串拼接;如果是纯对象、数组、正则等,则默认调用对象的转换方法会存在优先级,然后再进行拼接。
5、其中有一个是数字,另外一个是 undefined、null、布尔型或数字,则会将其转换成数字进行加法运算,对象的情况还是参考上一条规则。

’==’ 的隐式类型转换规则
1、如果类型相同,无须进行类型转换;
2、只要都是null、undefined, 无条件返回true,否则都是false
3、只要一方是NaN,没有隐式类型转换,无条件返回false
4、都是string,boolean,number。会将不是number类型的数据转成number
5、有一方是复杂数据类型 。 会先获取复杂数据类型的原始值之后再左比较
6、都是复杂数据类型。比较地址,如果地址一致则返回true,否则返回false
7、如果其中一个是 Symbol 类型,那么返回 false

console.log ( null == undefined );//true
console.log ( null == null );//true
console.log ( undefined == undefined );//trueconsole.log ( NaN == NaN );//falseconsole.log ( 1 == true );//true    (1) 1 == Number(true)
console.log ( 1 == "true" );//false   (1) 1 == Number('true')
console.log ( 1 == ! "true" );//false  (1) 1 == !Boolean('true')  (2) 1 == !true  (3) 1 == false  (4)1 == Number(false)
console.log ( 0 == ! "true" );//true
console.log(true == 'true') // falseconsole.log ( [].toString () );//空字符串
console.log ( {}.toString () );//[object Object]
console.log ( [ 1, 2, 3 ].valueOf().toString());//‘1,2,3’
console.log ( [ 1, 2, 3 ] == "1,2,3" );//true  (1)[1,2,3].toString() == '1,2,3'  (2)'1,2,3' == '1,2,3'
console.log({} == '[object Object]');//truevar arr1 = [10,20,30];
var arr2 = [10,20,30];
var arr3 = arr1;
console.log ( arr1 == arr2 );// false 它们是两个不同的地址
console.log ( arr3 == arr1 );//true  两者地址是一样
console.log ( [] == [] );//false
console.log ( {} == {} );//false

强制转换

Number():

原始值转换结果
undefinedNaN
null0
true /false1/0
字符串’‘ => 0 ;‘1’ => 1;’a‘ => NaN;’1a‘ => NaN;’1.23‘ => 1.23; ’Ox11‘ => 17;
symbol报错
object{} => 0;[] => 0; [1] => 1;[1,2,3] => NaN;先调用toPrimitive,再调用toNumber

parseInt(),parseFloat():

除了数值和字符串,其他都为NaN
相比Number没那么严格,函数逐个解析字符,遇到不能转换的字符就停下来

在这里插入图片描述
toString(),String()

可以将任意类型的值转化成字符串

原始值转换结果
undefined’undefined ‘
true /false‘true’ / ‘false’
number对应的字符串类型
symbol报错
object{} => 0;[] => 0; [1] => 1;[1,2,3] => NaN;先调用toPrimitive,再调用toNumber

【题】类型转换,经典面试题

注意:八种情况转boolean得到false: 0 -0 NaN undefined null '' false document.all()console.log([] == 0); //true // 分析:(1) [].valueOf().toString() == 0  (2) Number('') == 0  (3) false == 0  (4) 0 == 0
console.log(![] == 0); //true// 分析: 逻辑非优先级高于关系运算符 ![] = false (空数组转布尔值得到true)console.log([] == []); //false
// [] 与右边逻辑非表达式结果比较
//(1) [] == !Boolean([])   (2) [] == !true  (3)[] == false  (4) [].toString() == false  (5)'' == false   (6)Number('0') == Number(false)
console.log([] == ![]); //trueonsole.log({} == {}); //false
// {} 与右边逻辑非表达式结果比较
//(1){} == !{} (2){} == !true  (3){} == false  (4){}.toString() == false  (5)'[object Object]' == false  (6)Number('[object Object]') == false
console.log({} == !{}); //false

JS各种运算符

  • 算数运算符

y = 5
在这里插入图片描述
"+"作为二元运算符规则

  1. Infinity + Infinity = Infinity
  2. (-Infinity) + (-Infinity) = -Infinity
  3. Infinity + (-Infinity) = NaN
  4. Infinity + null = Infinity
  5. 0 + 0 = 0
  6. -0 + -0 = -0
  7. +0 + -0 = +0
  8. 如果是两个字符串,则将字符串进行拼接
  9. 如果有一个是字符串一个不是字符串,则将非字符串转换成字符串再拼接
  10. 如果操作数都不是字符串类型,则将操作数转换为数值类型再相加

"+"作为一元运算符

作用:于Number()相同,将其他类型的对象转换为数字类型

"+,-,*,/, %"运算符相同规则

  1. 与NaN相关的运算结果都为NaN
  2. 如果有一个操作数是对象,则先对对象进行隐式转换,再根据前面的规则进行运算
  3. 如果果有一个操作数是非数字类型则先在后台调用 Number()函数将其转换为数值,再根据前面的规则进行运算。

"-"运算符规则

  1. Infinity - Infinity = NaN
  2. (-Infinity) - (-Infinity) = -NaN
  3. Infinity - (-Infinity) = Infinity
  4. Infinity - null = Infinity
  5. 0 - 0 = 0
  6. -0 - -0 = -0
  7. +0 - -0 = +0
  • fdf

"*"运算符规则

  1. Infinity * Infinity = Infinity
  2. 如果操作数的值超过数值的表示范围,结果为Infinity 或 - Infinity
  3. 如果操作数中有一个操作数为NaN、0、null、undefined ,结果为NaN

"/"运算符规则

  1. Infinity / 0 = Infinity
  2. Infinity / null = Infinity
  3. Infinity / Infinity = NaN
  4. 如果操作数的值超过数值的表示范围,结果为Infinity 或 - Infinity
  5. 如果操作数中有一个操作数为NaN、undefined,结果为NaN

"%"运算符规则

  1. 操作数都是数值,执行常规的除法计算,返回除的余数
  2. 任何数 % undefined = NaN
  3. 任何数 % NaN =
    NaN
  4. Infinity % 任何数 = NaN
  5. 有限大的数 % 0 = NaN
  6. 有限大的数 % Infinity =
    有限大的数
  7. 0 % 除null、undefined、NaN任何数 = 0
  8. 如果有一个操作数是对象,则先对对象进行隐式转换,再根据前面的规则进行取余运算
  9. 有一个操作数不是数值,则调用Number()转换

一次弄懂JavaScript的各种运算符

数组

1、创建数组

const arr = []; // 字面量
const arr = [1, 2, 3, 4];
const arr = new Array(); // 构造函数, 不推荐
const arr = new Array(5);

fill() 创建一个长度确定、同时每一个元素的值也都确定的数组

const arr = (new Array(5)).fill(1); // [1,1,1,1,1]

2、增删查改方法

数组基本操作可以归纳为 增、删、改、查,需要留意的是哪些方法会对原数组产生影响,哪些方法不会

【增】:push(),unshift(),splice(),concat()

前三种方法会对原数组产生影响,concat() 不会对原数组产生影响

push()

在数组末尾添加任意多个值,返回数组的最新长度

const arr = []; // 创建一个数组
const count = arr.push(1, 2); // 推入两项
console.log(arr) // [1, 2]
console.log(count) // 2

unshift()

在数组开头添加任意多个值,返回数组的最新长度

const arr = []; // 创建一个数组
const count = arr.unshift(3); // 推入两项
console.log(arr) // [3, 1, 2]
console.log(count) // 3

splice(开始位置, 0, 插入的元素)

返回空数组

const arr = [1, 2, 3]; 
let removed = arr.splice(1, 0, 4, 5)
console.log(arr) // [1, 4, 5, 2, 3]
console.log(removed) // []

concat()

创建一个当前数组的副本,然后再把它的参数添加到副本末尾,返回新构建的数组不会影响原始数组

const arr = [1, 2, 3]; 
const arr1 = arr.concat(4, 5, 6);
console.log(arr) // [1, 2, 3]
console.log(arr1) // [1, 2, 3, 4, 5, 6]

【注意】:concat属于浅拷贝,所以会有以下情况:

const arr = [1, 2, 3, [1, 2, 3]]; 
const arr1 = arr.concat(4, 5, 6);
arr1[3][1]= 6
console.log(arr) // [1, 2, 3]
console.log(arr1) // [1, 2, 3, 4, 5, 6]

在这里插入图片描述
【删】:pop(),shift(),splice(),slice()

前三种方法会对原数组产生影响,slice() 不会对原数组产生影响

pop()

删除数组的最后一项,同时减少数组的length 值,返回被删除的项

shift()

删除数组的第一项,同时减少数组的length 值,返回被删除的项

splice(开始位置,删除元素的数量)

返回被删除元素的数组

const arr = [1, 2, 3, 4, 5 ,6]; 
const arr1 = arr.splice(4, 100);
console.log(arr) // [1, 2, 3, 4]
console.log(arr1) // [5, 6]

slice(开始位置,结束位置)

创建一个包含原有数组中一个或多个元素的新数组,不会影响原始数组

const arr = [1, 2, 3, 4, 5 ,6]; 
const arr1 = arr.slice(1);
const arr2 = arr.slice(1, 4);
console.log(arr)   // [1, 2, 3, 4, 5, 6]
console.log(arr1); // [2, 3, 4, 5, 6]
console.log(arr2); // [2, 3, 4]

【改】:splice()

splice(开始位置,结束位置,要替换为新的一个或多个元素)

返回被修改的元素的数组,对原数组产生影响

const arr = [1, 2, 3, 4, 5 ,6]; 
const arr1 = arr.splice(1, 1, 'two');
const arr2 = arr.splice(1, 1, 'two', 'three');
console.log(arr); // [1, "two", "three", 3, 4, 5, 6]
console.log(arr1); // [2]
console.log(arr2); // ["two"]

【查】:indexOf(), includes(), find()

indexOf()

返回元素在数组中的位置索引返回从头找到的第一个位置的索引,如果没找到则返回-1

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.indexOf(4) // 3

includes()

查找元素在数组中的位置,如果找到返回true,否则false

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.includes(4) // true

find()

返回第一个匹配的元素

const people = [{name: "Matt",age: 27},{name: "Nicholas",age: 29}
];
people.find((item, index, array) => item.age < 28) // {name: "Matt", age: 27}

3、排序方法

  • reverse() : 数组元素反向排列
  • sort()

sort()

默认正序

升序:arr.sort(function(a, b){return a - b});
降序:arr.sort(function(a, b){return b - a});
乱序:arr.sort((function(a, b){return 0.5 – Math.random() })
排序对象数组:

arr.sort((function(a, b){var x = a.type.toLowerCase();var y = b.type.toLowerCase();if (x < y) {return -1;}if (x > y) {return 1;}return 0;})

4、转换方法

  • join()
  • split()

join()

接收一个字符串分隔符,返回包含所有项的字符串

let colors = ["red", "green", "blue"];
alert(colors.join(",")); // red,green,blue
alert(colors.join("||")); // red||green||blue

split()

将字符串按某个字符切割,组成数组

let str = 'red|green|blue';
let colors1 = str.split('|');
alert(str); // red|green|blue
alert(colors1 ); // ["red", "green", "blue"]

5、迭代方法

不改变原数组

  • forEach() 对数组每一项都运行传入的函数,没有返回值
  • map() 对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组
  • some() 对数组每一项都运行传入的函数,如果有一项函数返回 true ,则这个方法返回 true
  • every() 对数组每一项都运行传入的函数,如果每一项函数都返回 true ,则这个方法返回 true
  • filter() 对数组每一项都运行传入的函数,过滤,返回为 true 的项组成的数组
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let result = numbers.forEach((item, index, array) => {// 执行某些操作
});
console.log(result) // undefined
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let mapResult = numbers.map((item, index, array) => item * 2);
let mapResult1 = numbers.map((item, index, array) => {});
console.log(mapResult) // [2,4,6,8,10,8,6,4,2]
console.log(mapResult1) // [undefined, undefined, undefined,...]
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let someResult = numbers.some((item, index, array) => item > 2);
console.log(someResult) // true
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let everyResult = numbers.every((item, index, array) => item > 2);
console.log(everyResult) // false
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let filterResult = numbers.filter((item, index, array) => item > 2);
console.log(filterResult); // 3,4,5,4,3

6、数组最高/最低数值

(1)先sort()排序,取头,尾对应最低最高
(2)Math.max(),Math.min()。接受的是参数序列
Math.max(…arr)等于Math.max(1,2,3)

Math.max([2,3,1]) // NaN
Math.max(...[2,3,1]) // 3, 等于 Math.max(2,3,1)

7、判断是否数组

  • arr instanceof Array
  • arr.constructor === Array
  • Object.prototype.toString.call(arr) === '[object Array]'
  • es6 Array.isArray(arr)
  • Array.prototype.isPrototypeOf(arr);
  • Object.getPrototypeOf(arr) === Array.prototype;

8、ES6新扩展

  • Array.of

用于将参数依次转化为数组中的一项,然后返回这个新数组,而不管这个参数是数字还是其他。

Array.of(8.0); // [8]
Array.of(8.0, 5); // [8, 5]
Array('8'); // ["8"]
  • Array.from

加工函数

Array.from(item, callback , obj)
item 必选,类似数组的对象;
callback  加工函数,新生成的数组会经过该函数的加工再返回
obj this 作用域,表示加工函数执行时 this 的值。
var obj = {0: 'a', 1: 'b', 2:'c', length: 3};
Array.from(obj, function(value, index){console.log(value, index, this, arguments.length);return value.repeat(3);   //必须指定返回值,否则返回 undefined
}, obj);
// return 的 value 重复了三遍,最后返回的数组为 ["aaa","bbb","ccc"]Array.from(obj, (value) => value.repeat(3));
//  控制台返回 (3) ["aaa", "bbb", "ccc"]
// String
Array.from('abc');         // ["a", "b", "c"]
// Set
Array.from(new Set(['abc', 'def'])); // ["abc", "def"]
// Map
Array.from(new Map([[1, 'ab'], [2, 'de']])); 
// [[1, 'ab'], [2, 'de']]

【题】js实现随机选取10–100之间的10个数字,存入一个数组,并排序?

const arr = [];
for (let i = 0; i < 10; i++) {/*Math.abs 转为正数Math.floor 向下取整加1是为了能够取到100*/const item = Math.abs(Math.floor(Math.random() * (10 - 100 + 1))) + 10;arr.push(item)
}
arr.sort();
console.log(arr);

【题】计算数组元素总和?

reduce()方法:接收两个参数,
第一个参数接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
第二个参数非必须,是计算的初始值。
如果数组为空,则不会调用reduce中传入的函数。

const array = [1, 2, 3, 4, 5];
const sum = array.reduce((total, currItem) => {// total计算后的和, currItem 当前项return total + currItem;
}, 0);
console.log(sum) // 15
let studentsArray = [{name: '张飞',age: 20},{name: '赵云',age: 10}
];
let ageSum = studentsArray.reduce((ageSum, currStudent) => {return ageSum + currentStudent.age;
},0)

[题] 数组扁平化?

6 种方式
1、递归
循环递归的方式,一项一项地去遍历,如果每一项还是一个数组,那么就继续往下遍历,利用递归程序的方法,来实现数组的每一项的连接。

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.push(arr[i]);}}return result;
}

2、利用 reduce 函数迭代
用 reduce 来实现数组的拼接,从而简化第一种方法的代码

function flatten(arr) {return arr.reduce(function(prev, next){return prev.concat(Array.isArray(next) ? flatten(next) : next)}, [])
}

3、扩展运算符实现
先用数组的 some 方法把数组中仍然是组数的项过滤出来,然后执行 concat 操作,利用 ES6 的展开运算符,将其拼接到原数组中,最后返回原数组

function flatten(arr) {while (arr.some(item => Array.isArray(item))) {arr = [].concat(...arr);}return arr;
}

4、split 和 toString 共同处理
数组会默认带一个 toString 的方法,所以可以把数组直接转换成逗号分隔的字符串,然后再用 split 方法把字符串重新转换为数组

function flatten(arr) {return arr.toString().split(',');
}

5、调用 ES6 中的flat
arr.flat([depth])。depth 是可以传递数组的展开深度(默认不填、数值是 1),即展开一层数组。参数也可以传进 Infinity,代表不论多少层都要展开。

function flatten(arr) {return arr.flat(Infinity);
}

6、正则和 JSON 方法共同处理
用 JSON.stringify 的方法先转换为字符串,然后通过正则表达式过滤掉字符串中的数组的方括号,最后再利用 JSON.parse 把它转换成数组

function flatten(arr) {let str = JSON.stringify(arr);str = str.replace(/(\[|\])/g, '');str = '[' + str + ']';return JSON.parse(str); 
}

在这里插入图片描述

[题] 数组去重

数组去重

1、ES6

function unique (arr) {return Array.from(new Set(arr))
}

2、for嵌套for,然后splice去重(ES5中最常用)

function unique(arr){            for(var i=0; i<arr.length; i++){for(var j = i+1; j<arr.length; j++){if(arr[i] == arr[j]){         //第一个等同于第二个,splice方法删除第二个arr.splice(j,1);j--;}}}return arr;
}

3、indexOf去重

function unique(arr) {if (!Array.isArray(arr)) {console.log('type error!')return}var array = [];for (var i = 0; i < arr.length; i++) {if (array.indexOf(arr[i]) === -1) {array.push(arr[i])}}return array;
}

对象

【对象】无序属性的集合,其属性可以包含基本值,对象或者函数

对象的创建

1、工厂模式
通过在函数内部创建一个对象,为其添加属性和方法,并将对象返回,从而实现创建多个对象的目的
优点:能够解决创建多个对象的问题,兼容各个浏览器
缺点:没有解决对象识别的问题,不能知道一个对象的类型

function createPerson(name, age) {var o = new Object();o.name = name;o.age = age;o.sayName = function() {console.log(this.name);};return o;
}
var person1 = createPerson("Nicholas", 29);
var person2 = createPerson("Greg", 27);

2、构造函数模式
通过创建自定义的构造函数,从而定义自定义对象类型的属性和方法
优点:可以创建多个对象,解决对象的识别问题
缺点:每个实例都会创建不同的function实例,而其实创建完成同样任务的function实例是很没有必要的

// 为了区别构造函数和其它函数,构造函数需要以一个大写字母开头
function Person(name, age) {this.name = name;this.age = age;this.sayName = function() {console.log(this.name);}
}var person1 = new Person("Nicholas", 29);
var person2 = new Person("Greg", 27);

添加链接描述

字符串

字符串的常用方法

字符串常用的操作方法归纳为增、删、改、查

  • concat() 将一个或多个字符串拼接成一个新字符串
  • slice()
  • substr()
  • substring()

三个方法都返回调用它们的字符串的一个子字符串,而且都接收一或两个参数。不改变原字符串

let stringValue = "hello world";
console.log(stringValue.slice(3)); // "lo world"
console.log(stringValue.substring(3)); // "lo world"
console.log(stringValue.substr(3)); // "lo world"
console.log(stringValue.slice(3, 7)); // "lo w"
console.log(stringValue.substring(3,7)); // "lo w"
console.log(stringValue.substr(3, 7)); // "lo worl"


不是改变原字符串,而是创建字符串的一个副本,再进行操作

  • trim()、trimLeft()、trimRight() 删除前、后或前后所有空格符,再返回新的字符串
  • repeat()
  • padStart()、padEnd()
  • toLowerCase()、 toUpperCase()

浅拷贝 / 深拷贝

在这里插入图片描述

浅拷贝

如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址

浅拷贝的限制所在——它只能拷贝一层对象。如果存在对象的嵌套,那么浅拷贝将无能为力。因此深拷贝就是为了解决这个问题而生的,它能解决多层对象嵌套问题,彻底实现拷贝

常见的浅拷贝方式有:

  • Object.assign
  • Array.prototype.slice(), Array.prototype.concat()
  • 使用拓展运算符(...)实现的复制

Object.assign
object.assignES6object 的一个方法,该方法可以用于 JS 对象的合并等多个用途,其中一个用途就是可以进行浅拷贝

  • 它不会拷贝对象的继承属性;
  • 它不会拷贝对象的不可枚举的属性;
  • 可以拷贝 Symbol 类型的属性。
const obj1 = {age: 18,name: {name1: 'huahua',name2: 'Lotus'}
}
// const obj2 = {};
// Object.assign(obj2, obj1 );
// 或者
var obj2 = Object.assign({}, obj1 );
obj2.age  = 20
obj2.name.name1 = 'lala'
console.log(obj1);
console.log(obj2);

在这里插入图片描述
slice()

const arr = [1,2,3];
const arr1 = arr.slice(0);
arr1[1] = 0;
console.log(arr); // [1, 2, 3]
console.log(arr1); // [1, 0, 3]

concat()

const arr = [1,2,3];
const arr1 = arr.concat();
arr1[1] = 0;
console.log(arr); // [1, 2, 3]
console.log(arr1); // [1, 0, 3]

拓展运算符(…)

const obj1 = {age: 18,name: {name1: 'huahua',name2: 'Lotus'}
}
const obj2 = {...obj1};
obj2.age  = 20
obj2.name.name1 = 'lala'
console.log(obj1);
console.log(obj2);

手工实现一个浅拷贝

  • 对基础类型做一个最基本的一个拷贝;
  • 对引用类型开辟一个新的存储,并且拷贝一层对象属性。
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;}
}

深拷贝

将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。

常见的深拷贝方式有:

  • 乞丐版(JSON.stringify)
  • 手写循环递归
  • _.cloneDeep()
  • jQuery.extend()

JSON.stringify()

JSON.stringify() 是目前开发过程中最简单的深拷贝方法

其实就是把一个对象序列化成为 JSON 的字符串,并将对象里面的内容转换成字符串,最后再用 JSON.parse() 的方法将 JSON 字符串生成一个新的对象
但是这种方式存在弊端,会忽略undefinedsymbol函数

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 引用类型会变成字符串
  • 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null
  • 不能解决循环引用的对象,即对象成环 (obj[key] = obj)。

虽然到目前为止还有很多无法实现的功能,但是这种方法足以满足日常的开发需求,并且是最简单和快捷的。

基础版(手写递归实现)

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

区别

在这里插入图片描述

  • 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
  • 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址

[题] js实现一个clone函数 | 深拷贝?

const cloneFunc = (target) => {if (typeof target === 'object' && target !== null) {const cloneTarget = Array.isArray(target) ? []: {};for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] = deepClone(target[prop]);}}return cloneTarget;} else {return target;}
}

This

函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别
不同情况的调用,this指向分别如何。顺带可以提一下 es6 中箭头函数没有 this, arguments, super 等,这些只依赖包含箭头函数最接近的函数

this绑定规则:

1、默认绑定:全局环境中定义的 函数,内部使用this关键字 一定是 window
严格模式下,this会绑定到undefined

var a = 1
function foo() {console.log(this.a)
}
foo() // 1, 不管 `foo` 函数被放在了什么地方,`this` 一定是 `window`

2、隐式绑定:函数作为某个对象的方法调用,这时this就指这个上级对象

function foo() {console.log(this.a)
}
const obj = {a: 2,foo: foo
}
obj.foo() // 2

谁调用了函数,谁就是 this,所以在这个场景下 foo 函数中的 this 就是 obj 对象
特殊情况:
这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象

var o = {a:10,b:{fn:function(){console.log(this.a); //undefined}}
}
o.b.fn();

this永远指向的是最后调用它的对象,虽然fn是对象b的方法,但是fn赋值给j时候并没有执行,所以最终指向window

var o = {a:10,b:{a:12,fn:function(){console.log(this.a); //undefinedconsole.log(this); //window}}
}
var j = o.b.fn;
j();

3、new绑定:通过构建函数new关键字生成一个实例对象,此时this指向这个实例对象

function foo() {this.a = 1;console.log(this.a)
}
const c = new foo() // 1

new 的方式使 this 被永远绑定在了 c 上面,不会被任何方式改变 this
特殊情况:
new过程遇到return一个对象,此时this指向为返回的对象,null虽然也是对象,但是此时new仍然指向实例对象

function fn()  
{  this.user = 'xxx';  return {};  
}
var a = new fn();  
console.log(a.user); //undefined

如果返回一个简单类型的时候,则this指向实例对象

function fn()  
{  this.user = 'xxx';  return 1;
}
var a = new fn;  
console.log(a.user); //xxx

4、显示修改:apply()、call()、bind()
bind 这些改变上下文的 API 了,对于这些函数来说,this 取决于第一个参数,如果第一个参数为空,那么就是 window

let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // => window

不管我们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定,所以上面的结果是 window

5、箭头函数:
箭头函数是没有 this 的,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this

function a() {return () => {return () => {console.log(this)}}
}
console.log(a()()()) // => window

在这个例子中,因为包裹箭头函数的第一个普通函数是 a,所以此时的 thiswindow
另外对箭头函数使用 bind这类函数是无效的。
在这里插入图片描述
在这里插入图片描述

  1. 在浏览器里,在全局范围内this 指向window对象;
  2. 在函数中,this永远指向最后调用他的那个对象;
  3. 构造函数中,this指向new出来的那个新的对象;
  4. call、apply、bind中的this被强绑定在指定的那个对象上;
  5. 箭头函数中this比较特殊,箭头函数this为父作用域的this,不是调用时的this.要知道前四种方式,都是调用时确定,也就是动态的,而箭头函数的this指向是静态的,声明的时候就确定了下来;
  6. 优先级:new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级

apply/call/bind

call、apply、bind作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向
call、apply、 bind 是挂在 Function 对象上的三个方法,调用这三个方法的必须是一个函数。
this 取决于第一个参数,如果第一个参数为空,那么 this 就是 window

func.call(thisArg, param1, param2, ...)
func.apply(thisArg, [param1,param2,...])
func.bind(thisArg, param1, param2, ...)

call、apply、 bind 区别:
在这里插入图片描述
1、三者都可以改变函数的this对象指向
2、三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefinednull,则默认指向全局window
3、三者都可以传参,但是apply是数组,而call是参数列表,且applycall是一次性传入参数,而bind可以分为多次传入

// 方式一:只在bind中传递函数参数
fn.bind(obj,1,2)()// 方式二:在bind中传递函数参数,也在返回函数中传递参数
fn.bind(obj,1)(2)

4、bind是返回绑定this之后的函数,apply、call 则是立即执行

实现一个 call 函数

Function.prototype.myCall = function (context = window) {context.fn = this  // 1 给 context 添加一个属性var args = [...arguments].slice(1);  // 2将 context 后面的参数取出来var result = context.fn(...args) // 3delete context.fn // 删除 fnreturn result
}
// 1 getValue.call(a, 'pp', '24') => a.fn = getValue
// 3 getValue.call(a, 'pp', '24') => a.fn('pp', '24')

实现一个 apply 函数

Function.prototype.myApply = function(context = window, ...args) {// 在context上加一个唯一值不影响context上的属性let key = Symbol('key')context[key] = this; let result = context[key](args); // 这里和call传参不一样delete context[key]; // 不删除会导致context属性越来越多return result;
}

实现一个 bind 函数

1、修改this指向
2、动态传递参数
3、兼容new关键字
Function.prototype.myBind = function (context = window) {// 判断调用对象是否为函数if (typeof this !== "function") {throw new TypeError("Error");}// 获取参数const args = [...arguments].slice(1),fn = this;return function Fn() {// 根据调用方式,传入不同绑定值return fn.apply(this instanceof Fn ? new fn(...arguments) : context, args.concat(...arguments)); }
}

变量提升

当执行 JS 代码时,会生成执行环境,只要代码不是写在函数中的,就是在全局执行环境中,函数中的代码会产生函数执行环境,只此两种执行环境。

在生成执行环境时,会有两个阶段。
第一个阶段【创建阶段】,JS 解释器会找出需要提升的变量和函数,并且给他们提前在内存中开辟好空间,函数的话会将整个函数存入内存中,变量只声明并且赋值为 undefined,
第二个阶段【代码执行阶段】,我们可以直接提前使用

  • 在提升的过程中,相同的函数会覆盖上一个函数,并且函数优先于变量提升
b() // call b secondfunction b() {console.log('call b fist')
}
function b() {console.log('call b second')
}
var b = 'Hello world'

执行上下文

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

当执行 JS 代码时,会产生三种执行上下文:
【全局执行上下文】只有一个,浏览器中的全局对象就是 window对象
【函数执行上下文】只有在函数执行的时候才会被创建,每次调用函数都会创建一个新的执行上下文
【eval 执行上下文】运行在 eval 函数中的代码,很少用而且不建议使用

【执行上下文】可以简单理解为一个对象,每个执行上下文中都有三个重要的属性:
【变量对象(VO)】包含变量、函数声明和函数的形参,该属性只能在全局上下文中访问
【作用域链(词法作用域)】
【 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
]

eg:
在这里插入图片描述

生命周期:创建阶段 → 执行阶段 → 回收阶段

代码执行过程:

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

作用域

【作用域】即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合
【作用域链】
可以把它理解成包含自身变量对象和上级变量对象的列表,通过 [[Scope]]属性查找上级变量
作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和 函数。

当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域

如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错

作用域分为三种:
【全局】作用域
【函数】作用域(也叫局部作用域)
【块级】作用域

全局作用域

全局变量是挂载在 window 对象下的变量,所以在网页中的任何位置你都可以使用并且访问到这个全局变量

// 全局变量
var greeting = 'Hello World!';
function greet() {console.log(greeting);
}
greet(); // 'Hello World!'

函数作用域

函数中定义的变量叫作函数变量,这个时候只能在函数内部才能访问到它,所以它的作用域也就是函数的内部,称为函数作用域

function getName () {var name = 'inner';console.log(name);
}
getName(); // inner
console.log(name); // undefined

当这个函数被执行完之后,这个局部变量也相应会被销毁。所以你会看到在 getName 函数外面的 name 是访问不到的

块级作用域

ES6 中新增了块级作用域,最直接的表现就是新增的 let ,const 关键词,使用 let ,const 关键词定义的变量只能在块级作用域中被访问,有“暂时性死区”的特点,也就是说这个变量在定义之前是不能被使用的。
{...} 花大括号里面所包括的,就是块级作用域

console.log(a) //a is not defined
if(true){let a = '123';console.log(a)// 123
}
console.log(a) //a is not defined

闭包

【闭包】其实就是一个可以访问其他函数内部变量的函数。创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以 访问到当前函数的局部变量。

在这里插入图片描述

因为通常情况下,函数内部变量是无法在外部访问的,使用闭包就具备实现了能在外部访问某个函数内部变量的功能,让这些内部变量的值始终可以保存在内存中。

function fun1() {var a = 1;return function(){console.log(a);};
}fun1();var result = fun1();
result();  // 1

闭包的用途:

1、匿名自执行函数。

从var声明的全局变量,或者有时候的函数只需要执行一次,其内部变量无需维护的情况考虑。

使用闭包,创建一个匿名的函数,并立即执行它,外部无法引用它内部的变量,执行完后很快就会被释放,这种机制不会污染全局对象。

var datamodel = {  table : [],  tree : {}  
};  (function(dm){  for(var i = 0; i < dm.table.rows; i++){  var row = dm.table.rows[i];  for(var j = 0; j < row.cells; i++){  drawCell(i, j);  }  }  
})(datamodel); 

2、缓存
设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,
那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,
然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。

var CachedSearchBox = (function(){  var cache = {},  count = [];  return {  attachSearchBox : function(dsid){  if(dsid in cache){//如果结果在缓存中  return cache[dsid];//直接返回缓存中的对象  }  var fsb = new uikit.webctrl.SearchBox(dsid);//新建  cache[dsid] = fsb;//更新缓存  if(count.length > 100){//保正缓存的大小<=100  delete cache[count.shift()];  }  return fsb;        },  clearSearchBox : function(dsid){  if(dsid in cache){  cache[dsid].clearSelection();    }  }  };  
})();  
CachedSearchBox.attachSearchBox("input1");  

3、实现封装
4、现面向对象中的对象

function Person(){  var name = "default";     return {  getName : function(){  return name;  },  setName : function(newName){  name = newName;  }  }  
};  var john = Person();  
console.log(john.getName());  
john.setName("john");  
console.log(john.getName());  

闭包的表现形式:

  • 返回一个函数
  • 在定时器、事件监听、Ajax 请求、Web Workers 或者任何异步中,只要使用了回调函数,实际上就是在使用闭包
// 定时器
setTimeout(function handler(){console.log('1');
}1000);
// 事件监听
$('#app').click(function(){console.log('Event Listener');
});
  • 作为函数参数传递的形式
  • IIFE(立即执行函数),创建了闭包,保存了全局作用域(window)和当前函数的作用域,因此可以输出全局的变量
var a = 2;
(function IIFE(){console.log(a);  // 输出2
})();

[题] 大厂面试,循环输出问题?

for(var i = 0; i <= 5; i ++){setTimeout(function() {console.log(i)}, 0)
}
// 结果: 5, 5, 5, 5, 5

让你实现输出 1、2、3、4、5 的话怎么办呢?

1、使用 ES6 中的 let(推荐)

for(let i = 0; i <= 5; i ++){setTimeout(function() {console.log(i)}, 0)
}
// 结果: 1, 2, 3, 4, 5

2、可以利用 IIFE(立即执行函数),当每次 for 循环时,把此时的变量 i 传递到定时器中,然后执行

for(var i = 1;i <= 5;i++){(function(j){setTimeout(function timer(){console.log(j)}, 0)})(i)
}
// 结果: 1, 2, 3, 4, 5

3、定时器传入第三个参数

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

new操作符

【new 操作符】主要作用就是执行一个构造函数、返回一个实例对象

【new 操作符】内部执行步骤?
1、创建一个新对象
2、将对象与构建函数通过原型链连接起来
3、将构建函数中的this绑定到新建的对象obj上
4、根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理,返回新对象

function Person(name, age){this.name = name;this.age = age;
}
const person1 = new Person('Tom', 20)
console.log(person1)  // Person {name: "Tom", age: 20}

在这里插入图片描述
去掉 new,有什么不一样呢?

function Person(name, age){this.name = name;this.age = age;
}
const person1 = Person('Tom', 20)
console.log(person1)  // undefined, 默认情况下 this 的指向是 window

当构造函数中有 return 一个对象的操作,结果又会是什么样子呢?

function Person(name, age){this.name = name;this.age = age;return {age: 18}
}
const person1 = new Person('Tom', 20)
console.log(person1)  // {age: 18}
console.log(person1.name) // undefined
console.log(person1.age) // 18

结论:new 关键词执行之后总是会返回一个对象,要么是实例对象,要么是 return 语句指定的对象

手工实现new的过程:

function create(fn, ...args) {// 1.创建一个新对象const obj = {}// 2.新对象原型指向构造函数原型对象obj.__proto__ = fn.prototype// 3.将构建函数的this指向新对象let result = fn.apply(obj, args)// 4.根据返回值判断return result instanceof Object ? result : obj
}

合并简写:

function create(fn, ...args) {if(typeof fn !== 'function') {throw 'fn must be a function';}// 1、2步骤合并const obj = Object.create(fn.prototype);// 3、执行fn,并将obj作为内部this。var res = fn.apply(obj, args);// 4、如果fn有返回值,则将其作为new操作返回内容,否则返回objreturn res instanceof Object ? res : obj;
};

测试使用:

function Person(name, age) {this.name = name;this.age = age;
}
Person.prototype.say = function () {console.log(this.name)
}let p = create(Person, "huihui", 123)
console.log(p) // Person {name: "huihui", age: 123}
p.say() // huihui

原型 / 原型链

【原型】一个简单的对象,用于实现对象的 属性继承。每个对象拥有一个原型对象。每个JavaScript对象中都包含一个__proto__(非标准)的属性指向该对象的原型,可obj.__proto__(非标准)进行访问。ES5 中新增了一个 Object.getPrototypeOf() 方法,我们可以通过这个方法来获取对象的原型。
【原型链】
原型链是由原型对象组成,每个对象都有 proto 属性,指向了创建该对象的构造函数的原型,proto 将对象连接起来组成了原型链。是一个用来实现继承和共享属性的有限的对象链

当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。

三者的关系:

  • 实例.proto === 原型
  • 原型.constructor === 构造函数
  • 构造函数.prototype === 原型

js 获取原型的方法:

  • obj.__proto__
  • obj.constructor.prototype
  • Object.getPrototypeOf(p)

在这里插入图片描述

person.__proto__ === Person.prototype // 对象的__proto__指向它的构造函数的原型对象prototype
Person.__proto__ === Function.prototype // 构造函数的__proto__指向Function构造器的原型对象prototype
Person.prototype.__proto__ === Object.prototype // 原型对象本身是一个普通对象,而普通对象的构造函数都是Object
Object.__proto__ === Function.prototype // 构造器都是函数对象,函数对象都是 Function构造产生的
Object.prototype.__proto__ === null // Object的原型对象也有__proto__属性指向null,null是原型链的顶端

继承

继承(inheritance)是面向对象软件技术当中的一个概念
如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”

继承的优点:

  • 继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码
  • 在子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能

写一个类,实现继承:

class Car{constructor(color,speed){this.color = colorthis.speed = speed// ...}
}// 货车 继承
class Truck extends Car{constructor(color,speed){super(color,speed);this.color = "black" //覆盖this.Container = true // 货箱}
}

常见的继承方式:

  • 原型链继承
  • 构造函数继承(借助 call)
  • 组合继承
  • 原型式继承
  • 寄生式继承
  • 寄生组合式继承
  • Class 继承
    在这里插入图片描述

原型链继承:涉及构造函数、原型和实例,和三者之间存在的关系

 function Parent() {this.name = 'parent1';this.play = [1, 2, 3]}function Child() {this.type = 'child2';}Child.prototype = new Parent();var s1 = new Child();var s2 = new Child();s1.play.push(4);console.log(s1.play, s2.play); // [1,2,3,4]

问题:通过子类生成的实例使用的是同一个原型对象,内存空间是共享的,一发而动全身

构造函数继承:借助 call调用Parent函数

function Parent(){this.name = 'parent1';
}Parent.prototype.getName = function () {return this.name;
}function Child(){Parent1.call(this);this.type = 'child'
}let child = new Child();
console.log(child);  // 没问题
console.log(child.getName());  // 会报错

问题:相比第一种原型链继承方式,父类的引用属性不会被共享,优化了第一种继承方式的弊端,但是只能继承父类的实例属性和方法,不能继承原型属性或者方法

组合继承:组合继承则将前两种方式继承起来

function Parent3 () {this.name = 'parent3';this.play = [1, 2, 3];
}Parent3.prototype.getName = function () {return this.name;
}
function Child3() {// 第二次调用 Parent3()Parent3.call(this);this.type = 'child3';
}// 第一次调用 Parent3()
Child3.prototype = new Parent3();
// 手动挂上构造器,指向自己的构造函数
Child3.prototype.constructor = Child3;

问题:Parent3 执行了两次,造成了多构造一次的性能开销

原型式继承:借助Object.create方法实现普通对象的继承

let parent4 = {name: "parent4",friends: ["p1", "p2", "p3"],getName: function() {return this.name;}};const person4 = Object.create(parent4);const person5 = Object.create(parent4);person5.friends.push("lucy");console.log(person4.friends); // ["p1", "p2", "p3","lucy"]console.log(person5.friends); // ["p1", "p2", "p3","lucy"]

问题:Object.create方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能

寄生式继承:在上面继承基础上进行优化,利用这个浅拷贝的能力再进行增强,添加一些方法

let parent5 = {name: "parent5",friends: ["p1", "p2", "p3"],getName: function() {return this.name;}
};function clone(original) {let clone = Object.create(original);clone.getFriends = function() {return this.friends;};return clone;
}let person5 = clone(parent5);
console.log(person5.getName()); // parent5
console.log(person5.getFriends()); // ["p1", "p2", "p3"]

问题:跟上面讲的原型式继承一样

寄生组合式继承:是所有继承方式里面相对最优的继承方式

// 父类
function Parent6() {this.name = 'parent6';this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {return this.name;
}
// 子类
function Child6() {Parent6.call(this); // this.friends = 'child5';
}// 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;Child6.prototype.getFriends = function () {return this.friends;
}let person6 = new Child6(); 
console.log(person6); //{friends:"child5",name:"child5",play:[1,2,3],__proto__:Parent6}
console.log(person6.getName()); // parent6
console.log(person6.getFriends()); // child5

class 继承
class 实现继承的核心在于使用 extends 表明继承自哪个父类,并且在子类构造函数中必须调用 super,因为这段代码可以看成 Parent.call(this, value)。

class Parent {constructor(value) {this.val = value}getValue() {console.log(this.val)}
}
class Child extends Parent {constructor(value) {super(value); // 等于 Parent.call(this, value)this.val = value}
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true

面向对象

事件机制

【事件】javascript中的事件,可以理解就是在HTML文档或者浏览器中发生的一种交互操作,使得网页具备互动性, 常见的有加载事件、鼠标事件、自定义事件等

【事件流】是一个事件沿着特定数据结构传播的过程。
由于DOM是一个树结构,如果在父子节点绑定事件时候,当触发子节点的时候,就存在一个顺序问题,这就涉及到了事件流的概念

事件流都会经过三个阶段:捕获阶段 -> 目标阶段 -> 冒泡阶段

捕获 / 冒泡:

【事件冒泡】是一种从下往上的传播方式,由最具体的元素(触发节点)然后逐渐向上传播到最不具体的那个节点,也就是DOM中最高层的父节点
【事件捕获】最开始由不太具体的节点最早接收事件, 而最具体的节点(触发节点)最后接收事件

在这里插入图片描述

事件对象

标准 Event 属性方法

【type】返回当前 Event 对象表示的事件的名称
【target】返回触发此事件的元素(目标节点)
【currentTarget】返回其事件监听器触发该事件的元素

【bubbles】返回布尔值,事件是否是起泡事件类型
【cancelable】返回布尔值,事件是否可拥有取消的默认动作
【eventPhase】返回事件传播的当前阶段
【timeStamp】返回事件生成的日期和时间
【initEvent()】初始化新创建的Event对象的属性
【preventDefault()】告知浏览器不要执行与事件相关的默认动作(阻止默认事件)
【stopPropagation()】不再派发事件(阻止冒泡)

IE Event 属性方法
在这里插入图片描述

阻止冒泡 / 捕获

  • 阻止事件默认动作:event.preventDefault()/event.returnValue= false
  • 阻止事件冒泡: event.stopPropagation()/ event.cancelBubble = true。希望事件只触发在目标上,其实该函数也可以阻止捕获事件。

其实stopImmediatePropagation 同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件

node.addEventListener('click',(event) =>{event.stopImmediatePropagation()console.log('冒泡')
},false);
// 点击 node 只会执行上面的函数,该函数不会执行
node.addEventListener('click',(event) => {console.log('捕获 ')
},true)

事件模型

事件模型可以分为三种:
原始事件模型(DOM0级):onclick、…
标准事件模型(DOM2级):addEventListener
IE事件模型(基本不用)

1、原始事件模型(DOM0级)

HTML代码直接绑定:<input type="button" onclick="fun()">
JS代码绑定:

var btn = document.getElementById('.btn');
btn.onclick = fun;

特性:

  • 绑定速度快
  • 只支持冒泡,不支持捕获
  • 一个类型的事件只能绑定一次
  • 删除 DOM0 级事件 btn.onclick = null;

2、标准事件模型(DOM2级)

事件共有三个阶段:捕获阶段 -> 目标阶段 -> 冒泡阶段

通常我们使用 addEventListener 注册事件

node.addEventListener(eventType, handler, useCapture)
eventType第一个参数:指定事件类型(不要加on)
handler第二个参数:是事件处理函数
useCapture第三个参数:可以是布尔值,也可以是对象。布尔值决定注册的事件是捕获事件还是冒泡事件, false(默认,冒泡) / true (捕获)

var div = document.getElementById('div');
var p = document.getElementById('p');function onClickFn (event) {var tagName = event.currentTarget.tagName;var phase = event.eventPhase;console.log(tagName, phase);
}
div.addEventListener('click', onClickFn, false);
p.addEventListener('click', onClickFn, false);

特性:

  • 可以在一个DOM元素上绑定多个事件处理器,各自并不会冲突
  • 执行时机。当第三个参数(useCapture)设置为true就在捕获过程中执行,反之在冒泡过程中执行处理函数

3、IE事件模型

IE事件模型共有两个过程:目标阶段 -> 冒泡阶段

事件绑定、移除监听函数的方式如下:

attachEvent(eventType, handler)
detachEvent(eventType, handler)

事件代理(又叫事件委托)

【事件代理】把一个元素响应事件(click、keydown…)的函数委托到另一个元素。而事件委托就是在冒泡阶段完成

【原理】是利用了事件冒泡原理实现的。当我们为最外层的节点添加点击事件,那么里面的ul、li、a的点击事件都会冒泡到最外层节点上,委托它代为执行事件

应用场景:

1、列表事件绑定

<ul id="ul"><li>1</li><li>2</li><li>3</li>
</ul>
window.onload = function(){var ulEle = document.getElementById('ul');ul.onclick = function(ev){//兼容IEev = ev || window.event;var target = ev.target || ev.srcElement;if(target.nodeName.toLowerCase() == 'li'){alert( target.innerHTML);}}
}

2、动态列表实现事件绑定
可以减少很多重复工作

<input type="button" name="" id="btn" value="添加" />
<ul id="ul1"><li>item 1</li><li>item 2</li><li>item 3</li><li>item 4</li>
</ul>
const oBtn = document.getElementById("btn");
const oUl = document.getElementById("ul1");
const num = 4;//事件委托,添加的子元素也有事件
oUl.onclick = function (ev) {ev = ev || window.event;const target = ev.target || ev.srcElement;if (target.nodeName.toLowerCase() == 'li') {console.log('the content is: ', target.innerHTML);}
};//添加新节点
oBtn.onclick = function () {num++;const oLi = document.createElement('li');oLi.innerHTML = `item ${num}`;oUl.appendChild(oLi);
};

适合事件委托的事件有:click,mousedown,mouseup,keydown,keyup,keypress
无法进行委托绑定事件的有:focus、blur,因为这些事件没有事件冒泡机制
mousemove、mouseout这样的事件也是不适合于事件委托的,因为要不断通过位置去计算定位,对性能消耗高

优点:
减少整个页面所需的内存,提升整体性能
动态绑定,减少重复工作

节流与防抖

【防抖】是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。
可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。

【节流】是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。
节流可以使用在 scroll 函数的滚动事件监听上,通过事件节流来降低事件调用的频率。

防抖的实现

function debounce(fn, wait) {var timer = null;return function() {var context = this,args = arguments;// 如果此时存在定时器的话,则取消之前的定时器重新记时if (timer) {clearTimeout(timer);timer = null;}// 设置定时器,使事件间隔指定事件后执行timer = setTimeout(() => {fn.apply(context, args);}, wait);};
}

节流的实现

function throttle(fn, delay) {var preTime = Date.now();return function() {var context = this,args = arguments,nowTime = Date.now();// 如果两次时间间隔超过了指定时间,则执行函数。if (nowTime - preTime >= delay) {preTime = Date.now();return fn.apply(context, args);}};
}

模块化

【模块(Module)】
将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起
块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信

模块化的进化过程

1、全局function模式:将不同的功能封装成不同的全局函数
作用:将不同的功能封装成不同的全局函数
问题:污染全局命名空间, 容易引起命名冲突或数据不安全,而且模块成员之间看不出直接关系

function m1(){//...
}
function m2(){//...
}

2、namespace模式 : 简单对象封装
作用:减少了全局变量,解决命名冲突
问题:数据不安全(外部可以直接修改模块内部的数据)

let myModule = {data: 'www.baidu.com',foo() {console.log(`foo() ${this.data}`)},bar() {console.log(`bar() ${this.data}`)}
}
myModule.data = 'other data' //能直接修改模块内部的数据
myModule.foo() // foo() other data

3、IIFE模式:立即执行函数(闭包)
问题: 如果当前这个模块依赖另一个模块怎么办?

// module.js文件
// 引入jQuery依赖
(function(window, $) {let data = 'www.baidu.com'//操作数据的函数function foo() {//用于暴露有函数console.log(`foo() ${data}`)$('body').css('background', 'red')}function bar() {//用于暴露有函数console.log(`bar() ${data}`)otherFun() //内部调用}function otherFun() {//内部私有的函数console.log('otherFun()')}//暴露行为window.myModule = { foo, bar } //ES6写法
})(window, jQuery)
// index.html文件
<!-- 引入的js必须有一定顺序 -->
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">myModule.foo()myModule.bar()console.log(myModule.data) //undefined 不能访问模块内部数据myModule.data = 'xxxx' //不是修改的模块内部的datamyModule.foo() //没有改变
</script>

在这里插入图片描述
模块化的好处
1、避免命名冲突(减少命名空间污染)
2、更好的分离, 按需加载
3、更高复用性
4、高可维护性

模块化引入的问题
1、请求过多:我们要依赖多个模块,那样就会发送多个请求,导致请求过多
2、依赖模糊:不知道他们的具体依赖关系是什么,很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。
3、难以维护:很可能出现牵一发而动全身的情况导致项目出现严重的问题

模块化固然有多个好处,然而一个页面需要引入多个js文件,就会出现以上这些问题。而这些问题可以通过模块化规范来解决,下面介绍开发中最流行的commonjs, AMD, ES6, CMD规范。

模块化规范

【模块化规范】即为 JavaScript 提供一种模块编写、模块依赖和模块运行的方案。降低代码复杂度,提高解耦性

1、CommonJS 方案
2、AMD (典型代表:require.js)
3、CMD (典型代表:sea.js)
4、ES6 提出的方案,使用 importexport 的形式来导入导出模块

模块化规范

1、CommonJs 方案

特点:
1、同步加载方式,适用于服务端,因为模块都放在服务器端,对于服务端来说模块加载较快,不适合在浏览器环境中使用,因为同步意味着阻塞加载。
2、所有代码都运行在模块作用域,不会污染全局作用域。
3、模块可以多次加载,但只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。如果想要再次执行,可清除缓存
4、模块加载的顺序,按照其在代码中出现的顺序。

服务器端实现:Node.js
浏览器端实现:Browserify(Commonjs的浏览器的打包工具)
通过 module.exports 、exports定义模块的输出接口
通过 require引入模块。读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。

// a.js
module.exports = {a: 1
}
// or
exports.a = 1// b.js
var module = require('./a.js')
module.a // -> log 1

module.exports 和exports,用法其实是相似的,但是不能对 exports 直接赋值,不会有任何效果。

为什么 exports 和 module.exports 用法相似的原因
var exports = module.exports
var load = function (module) {// 导出的东西var a = 1module.exports = areturn module.exports
};

2、AMD (典型代表:require.js)

异步模块定义,采用异步方式加载模块。所有依赖模块的语句,都定义在一个回调函数中,等到模块加载完成之后,这个回调函数才会运行
require.js 实现了 AMD 规范

/** main.js 入口文件/主模块 **/
// 首先用config()指定各模块路径和引用名
require.config({baseUrl: "js/lib",paths: {"jquery": "jquery.min",  //实际路径为js/lib/jquery.min.js"underscore": "underscore.min",}
});
// 执行基本操作
require(["jquery","underscore"],function($,_){// some code here
});

3、CMD (典型代表:sea.js)
都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。

4、 ES6 提出的方案,使用 importexport 的形式来导入导出模块

ES6 在语言标准的层面上,实现了Module,即模块功能,完全可以取代 CommonJSAMD规范,成为浏览器和服务器通用的模块解决方案

// fs.js
// ES6模块
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
function v1() { ... }
function v2() { ... }export { firstName, lastName, year, v1, v2 };// import后面我们常接着from关键字,from指定模块文件的位置,可以是相对路径,也可以是绝对路径
import { stat, exists, readFile } from 'fs';
// 通过as关键字起别名
import { v1 as  streamV1,v2 as  streamV2
} from 'fs';
// 当加载整个模块的时候,需要用到星号*
import * as circle from './circle';

ES6 模块与 CommonJS 模块的差异
1、前者支持动态导入,也就是 require(${path}/xx.js),后者目前不支持,但是已有提案
2、前者是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
3、前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是后者采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
4、后者会编译成 require/exports 来执行的

Promise

【promise】ES6 新增的语法,是异步编程的一种方式,比传统回调函数更合理更强大。
优点:链式操作,解决了地狱回调,降低了编码难度,代码可读性增强

Promise对象仅有三种状态:pending(进行中), fulfilled(已成功), rejected(已失败)
可以把 Promise看成一个状态机。初始是 pending 状态,可以通过函数 resolve 和 reject,将状态转变为 resolved 或者 rejected 状态,状态一旦改变就不能再次变化。

promise能链式书写的原因:
因为最后 Promise.prototype.then 和 Promise.prototype.catch 方法返回的是一个 Promise实例,
并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then 调用就失去意义了。

基本语法:

const promise = new Promise(function(resolve, reject) {if (true) {resolve('success');} else {reject('error')}
})
.then((resualt)=> {console.log(resualt)return Promise.resolve(resualt);
})
.then((data) => {// 以上 promise 成功状态的回调函数 console.log('thenTwo', data);return Promise.reject();
})
.then(data => {// 以上 promise 成功状态的回调函数 console.log('thenthree', data);
})
.catch(error => {// 处理前面所有Promise产生的错误console.log(error)
})
.finally(() => {// finally 前面所有Promise执行结束console.log('finally');
})

【Promise 】构造函数
【promise 】 一个带有状态和返回结果的 Promise 新实例
【resolve】回调函数,将Promise对象的状态从“未完成”变为“成功”
【reject】回调函数,将Promise对象的状态从“未完成”变为“失败”
【then()】 实例状态发生改变时的回调函数,第一个参数是resolved成功状态的回调函数,第二个参数是rejected失败状态的回调函数。
【catch()】 第二个参数,.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数
【finally()】不管 Promise 对象最后状态如何,都会执行的操作

【链式调用的终止/取消】方式:
第一种方法:throw new error() 抛出异常
第二种方法:return Promise.reject()

Promise实例 拥有以下方法:
then() :resolved成功状态的回调函数。返回值是一个新的实例
catch():rejected失败状态的回调函数。返回值是一个新的实例。是.then(null, rejection)或.then(undefined, rejection)的别名
finally():不管 Promise 对象最后状态如何,都能执行的操作

Promise构造函数 的静态方法:

all()
语法: Promise.all(iterable)
参数: 一个可迭代对象,如 Array。
描述:将多个 Promise实例,包装成一个新的 Promise实例。在 ES6 中可以将多个 Promise.all 异步请求并行操作,
返回结果一般有两种情况:
1、当所有结果成功返回时按照请求顺序返回成功结果。
2、当其中有一个失败方法时,则进入失败方法

业务场景:页面加载,多个请求合并

/* 在一个页面中需要加载获取轮播列表、获取店铺列表、获取分类列表这三个操作,
页面需要同时发出请求进行页面渲染,这样用 `Promise.all` 来实现,看起来更清晰、一目了然。
*///1.获取轮播数据列表
function getBannerList(){return new Promise((resolve,reject)=>{setTimeout(function(){resolve('轮播数据')},300) })
}
//2.获取店铺列表
function getStoreList(){return new Promise((resolve,reject)=>{setTimeout(function(){resolve('店铺数据')},500)})
}
//3.获取分类列表
function getCategoryList(){return new Promise((resolve,reject)=>{setTimeout(function(){resolve('分类数据')},700)})
}
function initLoad(){ Promise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res=>{console.log(res) }).catch(err=>{console.log(err)})
} 
initLoad()

race()

语法及参数跟 Promise.all 类似。唯一的不同在于,只要有一个实例率先改变状态,返回状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 race 方法的回调函数

业务场景:图片的加载超时判断

//请求某个图片资源
function requestImg(){var p = new Promise(function(resolve, reject){var img = new Image();img.onload = function(){ resolve(img); }img.src = 'http://www.baidu.com/img/flexible/logo/pc/result.png';});return p;
}
//延时函数,用于给请求计时
function timeout(){var p = new Promise(function(resolve, reject){setTimeout(function(){ reject('图片请求超时'); }, 5000);});return p;
}
Promise.race([requestImg(), timeout()])
.then(function(results){console.log(results);
})
.catch(function(reason){console.log(reason);
});

allSettled()

语法及参数跟 Promise.all 类似。唯一的不同在于,执行完之后不会失败。只有等到所有这些参数实例都返回结果,不管是成功还是失败,包装实例才会结束

const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {console.log(results);
});
// 返回结果:
// [
//    { status: 'fulfilled', value: 2 },
//    { status: 'rejected', reason: -1 }
// ]

any()
语法及参数跟 Promise.all 类似。唯一的不同在于,只要参数 Promise 实例有一个变成 fulfilled状态,最后 any返回的实例就会变成 fulfilled 状态;如果所有参数 Promise 实例都变成 rejected 状态,包装实例就会变成 rejected 状态。

const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const anyPromise = Promise.any([resolved, rejected]);
anyPromise.then(function (results) {console.log(results);
});
// 返回结果:
// 2

[题] 手写实现promise

function myPromise(fn){let that = this;that.status = 'pending'; // 定义状态管理that.value = undefined; // 定义成功状态的返回值that.reason = undefined; // 定义失败状态的返回值that.fulfilledCallbacks = [];that.rejectedCallbacks = [];function resolve(value){if (that.status === 'pending') { // 保证了状态的改变是不可逆的that.value = value;that.status = 'fulfilled';//执行回调方法that.fulfilledCallbacks.forEach(myFn => myFn(that.value))}}function reject(reason){if(that.status === 'pending'){ // 保证了状态的改变是不可逆的that.reason = reason;that.status = "rejected";}}//捕获构造异常try{fn(resolve, reject);} catch(e) {reject(e);}
}
// 定义链式调用的then方法
myPromise.prototype.then = function(onFullfilled, onRejected){let that = this;switch(that.status){case 'pending': that.fulfilledCallbacks.push(()=>{onFulfilled(that.value);});that.rejectedCallbacks.push(()=>{onRejected(that.reason);})case 'fulfilled':onFullfilled(that.value);break;case 'rejected':onRejected(that.reason);break;default:       }
}

async/await

Generator 函数的语法糖。有更好的语义、更好的适用性、返回值是 Promise

await 相比直接使用 Promise 来说,优势在于处理 then 的调用链,能够更清晰准确的写出代码。缺点在于滥用 await 可能会导致性能问题,因为 await 会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性,此时更应该使用 Promise.all。
一个函数如果加上 async ,那么该函数就会返回一个 Promise

基本用法

async function timeout (ms) {await new Promise((resolve) => {setTimeout(resolve, ms)    })
}
async function asyncConsole (value, ms) {await timeout(ms)console.log(value)
}
asyncConsole('hello async and await', 1000)

优缺点:
async/await的优势在于处理 then 的调用链,能够更清晰准确的写出代码,并且也能优雅地解决回调地狱问题。
当然也存在一些缺点,因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。

async原理
使用Generator函数+自动执行器来运作的

// 定义了一个promise,用来模拟异步请求,作用是传入参数++
function getNum(num){return new Promise((resolve, reject) => {setTimeout(() => {resolve(num+1)}, 1000)})
}//自动执行器,如果一个Generator函数没有执行完,则递归调用
function asyncFun(func){var gen = func();function next(data){var result = gen.next(data);if (result.done) return result.value;result.value.then(function(data){next(data);});}next();
}// 所需要执行的Generator函数,内部的数据在执行完成一步的promise之后,再调用下一步
var func = function* (){var f1 = yield getNum(1);var f2 = yield getNum(f1);console.log(f2) ;
};
asyncFun(func);

在执行的过程中,判断一个函数的promise是否完成,如果已经完成,将结果传入下一个函数,继续重复此步骤
每一个 next() 方法返回值的 value 属性为一个 Promise 对象,所以我们为其添加 then 方法, 在 then 方法里面接着运行 next 方法挪移遍历器指针,直到 Generator函数运行完成

[题] 小试牛刀

var a = 0
var b = async () => {a = a + await 10console.log('2', a) // -> '2' 10a = (await 10) + aconsole.log('3', a) // -> '3' 20
}
b()
a++
console.log('1', a) // -> '1' 1

首先函数b 先执行,在执行到 await 10 之前变量 a 还是 0,因为在 await 内部实现了 generators ,generators 会保留堆栈中东西,所以这时候 a = 0 被保存了下来
因为 await 是异步操作,遇到await就会立即返回一个pending状态的Promise对象,暂时返回执行代码的控制权,使得函数外的代码得以继续执行,所以会先执行 console.log(‘1’, a)
这时候同步代码执行完毕,开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 10
然后后面就是常规执行代码了

浏览器

HTTP

前端性能优化

性能问题无外乎两方面原因:渲染速度慢、请求时间长。

定位问题

技术选型

我们主要从PC端、公众号(移动端H5)、小程序三大平台进行前端的技术选型,并来说说选其技术的几大优势。

一个好的技术框架选型应该参考优秀的BAT公司,让我们来通过boss直聘看一下这些大公司都用了什么技术框架。
在这里插入图片描述
前端3大框架,分别是Angular、React与Vue。我们来比较一下各框架的优劣势:
Angular

学习成本很高
难调试+笨重

React

对于跨平台应用程序开发,React Native是一个理想的选择,因为它提供了现代功能,您可以轻松地找到资源

vue

Vue.js是超轻量级(但功能非常丰富)的框架,它们结合了AngularJS和React两者。
渐进式构建能力是 vue.js 最大的优势,vue 有一个简洁而且合理的架构,使得它易于理解和构建。vue 有一个强大的充满激情人群的社区,这为 vue.js 增加了巨大的价值,使得为一个空白项目创建一个综合的解决方案变得十分容易。学习成本比较亲民。
Vue有以下特点:
(1)API设计上简单,语法简单,学习成本低 ;
(2)构建方面不包含路由和ajax功能,使用vuex, vue-router ;
(3)指令(dom)和组件(视图,数据,逻辑)处理清晰 ;
(4)性能好,容易优化 ;
(5)基于依赖追踪的观察系统,并且异步队列更新 ;
(6)独立触发 ;
(7)v-model 实时渲染;
(8)适用于:模板和渲染函数的弹性选择 ;
(9)简单的语法及项目搭建 ;
(10)更快的渲染速度和更小的体积。

三大框架适用范围
在这里插入图片描述
vue框架脱颖而出的原因
(1)小程序可以使用mpvue , 公众号H5可以使用vux,app开发可以使用weex,PC端可以用nuxt.js,UI框架可以使用elementUI,以上框架都基于vue开发做到了跨平台,有更好的代码复用性,做到了开发习惯的统一;
(2)API文档简单,上手容易,学习成本比较亲民,华人开发,更接地气;
(3)BAT公司的招聘要求都有在使用该框架;

vue在各个平台上的技术选型

小程序:
wepy 一个类Vue开发风格的小程序框架

特性:
类Vue开发风格
支持组件化开发
支持NPM
支持Promise, 主动选择是否开启
支持ES2015
编译器:支持less/sass/TypeScript等开发
小程序性能优化
框架大小:24.3k+8.9k
wepy-redux数据管理
构建与编译工具:wepy-cli,编译配置:wepy.config.js

mpvue 框架基于 Vue.js 核心,mpvue 修改了 Vue.js 的 runtime 和 compiler 实现,使其可以运行在小程序环境中,从而为小程序开发引入了整套 Vue.js 开发体验。

特性:
组件化开发
完整Vue.js开发体验,全部.vue单文件组件
Vuex数据管理方案
webpack构建机制:自定义构建策略、开发阶段hotReload
支持npm
使用 Vue.js 命令行工具 vue-cli 快速初始化项目
H5代码转换编译成小程序目标代码能力(可使用html开发)
构建与编译工具:vue-cli,编译配置:build
配套设施
mpvue-loader
mpvue-webpack-target
postcss-mpvue-wxss
px2rpx-loader
其他

选择mpvue的原因,特性对比
在这里插入图片描述
公众号H5

Nuxt/Vue SSR,Nuxt.js框架,解决服务器端渲染问题和首屏加载时长问题,实现 Vue SSR。

App

首选WEEX,其次是Flutter,再者是React native。

PC端

Nuxt.js框架,解决服务器端渲染问题和首屏加载时长问题,实现 Vue SSR。

UI框架

PC端:element-ui APP端:view-ui、vux

CSS处理器

Stylus,stylus在追求代码的整洁性上取得了优异性的胜利。

添加链接描述

NetWork

我们先来看一下network面板:
在这里插入图片描述

优化方案

前端优化手段有哪些?

  • 【静态资源合并压缩(js,css, images)】请求数量优化
  • 【Gzip压缩】带宽优化,大小优化,
  • 【CDN】就近节点,减少DNS查找
  • 【按需加载】列表分页、
  • 【lazyload懒加载】 减少请求
  • 【骨架屏】优化白屏
  • 【web缓存】缓存ajax数据
  • 【减少重绘和重排】批量更新DOM样式
  • 【页面结构】将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出

HTML性能优化

  • 文件压缩
  • 合理使用标签,少嵌套,结构分划
  • js引入方页面底部,或者使用异步加载(async 、defer )
  • link 标签:通过预处理提升渲染速度(rel=“dns-prefetch”)
  • 搜索优化.meta 标签提取关键信息
    CSS性能优化

在CSS文件请求、下载、解析完成之前,CSS会阻塞渲染,浏览器将不会渲染任何已处理的内容

  • 【内联首屏关键CSS】通过内联css关键代码能够使浏览器在下载完html后就能立刻渲染。而外部引用css代码,在解析html结构过程中遇到外部css文件,才会开始下载css代码,再渲染

  • 【异步加载CSS】

  • 【资源压缩】

  • 【合理使用选择器】
    可以遵循以下规则:
    不要嵌套使用过多复杂选择器,最好不要三层以上
    使用id选择器就没必要再进行嵌套
    通配符和属性选择器效率最低,避免使用

  • 【减少使用昂贵的属性】
    在页面发生重绘的时候,昂贵属性如box-shadow/border-radius/filter/透明度/:nth-child等,会降低浏览器的渲染性能

  • 【不要使用@import】
    css样式文件有两种引入方式,一种是link元素,另一种是@import
    @import会影响浏览器的并行下载,使得页面在加载时增加额外的延迟
    多个@import可能会导致下载顺序紊乱

  • 减少重排操作,以及减少不必要的重绘

  • 了解哪些属性可以继承而来,避免对这些属性重复编写

  • cssSprite,合成所有icon图片,用宽高加上backgroud-position的背景图方式显现出我们要的icon图,减少了http请求

  • 把小的icon图片转成base64编码

  • CSS3动画或者过渡尽量使用transform和opacity来实现动画,不要使用left和top属性

  • 图片压缩使用

JS性能优化

兼容、适配问题

[题] 移动端 1px 问题

1、局部处理
meta标签中的 viewport属性 ,initial-scale 设置为 1
rem按照设计稿标准走,外加利用transfrome的scale(0.5) 缩小一倍即可;

<meta name="viewport" content="initial-scale=1.0" />
.border-top{position: relative;
}
.border-top:befor{content: '';width: 100%;height: 1px;background: #ddd;transform:scaleY(0.5);position:absolute;top: 0;left: 0;
}

2、全局处理
mate标签中的 viewport属性 ,initial-scale 设置为 0.5
rem 按照设计稿标准走即可

TypeScript

类型?

对值所具有的结构进行类型检查

  • 布尔值
  • 数字
  • 字符串
  • 联合类型(eg: string | number)
  • 数组
  • 元组Tuple(一个已知元素数量和类型的数组,各元素的类型不必相同)
  • 枚举enum(可以为一组数值赋予友好的名字,可以手动的指定成员的数值)
  • 任意值any(我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查)
  • 空值void(与any类型相反,它表示没有任何类型,本身的类型用处不是很大)
  • null 和 undefined(本身的类型用处不是很大)
  • never(永不存在的值的类型)
  • 类型断言 as
let boll:boolean = true;
let num:number = 1;
let str:string = 'hello';

联合类型

let a:string | number = 1;

数组

let arr1:number[] = [1,2,3];
let arr2:Array<number> = [1, 2,3];
let list: any[] = [1, true, "free"];

元组Tuple

let tup:[string, number, boolean] = ['hello', 1, true]

枚举enum

enum Color {Red, Green, Blue}
let c: Color = Color.Green;enum Color {Red = 1, Green = 2, Blue = 4}
let c: string= Color[2];

any

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

void
当一个函数没有返回值时,你通常会见到其返回值类型是void

function warnUser(): void {alert("This is my warning message");
}

声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined和null

let unusable: void = undefined;

as

let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length; // 或者
let strLength: number = (someValue as string).length;

接口(interface)

function printLabel(labelledObj: { label: string }) {console.log(labelledObj.label);
}let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);
  • 类型检查。我们只会去关注值的外形
  • 类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以。
interface LabelledValue {label: string;
}function printLabel(labelledObj: LabelledValue) {console.log(labelledObj.label);
}let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
  • 可选属性(在可选属性名字定义的后面加一个?符号)

好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。

interface SquareConfig {color?: string;width?: number;
}function createSquare(config: SquareConfig): { color: string; area: number } {let newSquare = {color: "white", area: 100};if (config.color) {// Error: Property 'clor' does not exist on type 'SquareConfig'newSquare.color = config.clor;}if (config.width) {newSquare.area = config.width * config.width;}return newSquare;
}let mySquare = createSquare({color: "black"});
  • 只读属性 readonly(定义一些对象属性只能在对象刚刚创建的时候修改其值)
    readonly
interface Point {readonly x: number;readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!

readonlyArray,它与Array相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!

readonly vs const

判断该用readonly还是const的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用const,若做为属性则使用readonly。

  • 函数类型

为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。

interface SearchFunc {(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {let result = source.search(subString);return result > -1;
}

Vue2 & Vue3

Vue中data为什么要返回函数?

Vue3新特性 ?

  • 数据响应重新实现(Es6的 proxy 代替Es5的 Object.defineProperty)
  • 源码使用TS重写,更好的代码推导
  • 虚拟DOM新算法(更快,更小)
  • 提供了 composition api,为了更好的逻辑复用和代码组织
  • 自定义渲染器(app、小程序、游戏开发)
  • Frament,模板可以有多个根元素

Vue2 & Vue3 响应原理对比?

  • vue2使用Object.defineProperty方法劫持数据+发布-订阅模式实现响应式数据
    缺点:
    无法检测到对象属性的动态添加和删除
    无法检测到数组的下标和length的属性的变更
    解决方案:
    利用Vue.$set动态添加对象属性
    利用Vue.$delete动态删除对象属性
    重写数组的方法,检测数组变更
  • vue3使用 proxy 实现响应式数据
    优点:
    可以检测到代理对象属性的动态添加和删除
    可以检测到数组的下标和length属性的变化
    缺点:
    ES6的 proxy 不支持低版本你浏览器 IE11

Vue3有了解过吗?能说说跟Vue2的区别吗?

小试牛刀

1. JS中 ??|| 的区别?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/401373.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows文件被占用解决办法

我们有时会遇到某个文件被占用&#xff0c;无法删除或者修改。很多人此时重启机器来解决&#xff0c;但是因为有的程序已启动就把文件占用了&#xff0c;重启也没用。 其实&#xff0c;我们可以使用perfmon.exe /res 在上面的搜索框里输入被占用的文件名&#xff0c;就可以知道…

配置yum,nc,telnet

一、学习中问题   最近学习在学习Hadoop的一个子项目Zookeeper&#xff0c;在测试其中的“四字命令”---”echo ruok|nc localhost 2181“时发现命令无法被识别&#xff0c;如下图所示&#xff1a; [roothadoop ~]# echo ruok|nc localhost 2181 -bash: nc: command not foun…

20145318赵一《网络对抗》后门原理与实践

20145318赵一《网络对抗》后门原理与实践 知识点 后门 后门程序一般是指那些绕过安全性控制而获取对程序或系统访问权的程序方法。  在软件的开发阶段&#xff0c;程序员常常会在软件内创建后门程序以便可以修改程序设计中的缺陷。但是&#xff0c;如果这些后门被其他人知道&…

Nagios:企业级系统监控方案

在大多数情况下Cacti RRDtool已经实现对系统各种参数的监测。但很多企业可能不满足于仅仅监测系统基本参数的需求&#xff0c;而是需要监测除基本参数之外的各种应用程序的运行状况。很显然在这种情况下对于一些系统或者是自定义的程序Cacti RRDtool的局限性就显示出来了。而…

Universal-Image-Loader解析(二)——DisplayImageOptions的详细配置与简单的图片加载...

在使用这个框架的时候&#xff0c;我们必须要配置一个DisplayImageOptions对象来作为ImageLoader.getInstance().displayImage&#xff08;&#xff09;中的参数&#xff0c;所以很有必要讲解这个对象的配制方法。讲解完了后其实这个框架我们就会了解的比较详尽了。 1.默认的配…

React 进阶

React 进阶一、认识 React1、是什么&#xff1f;2、React 特性3、React 第一个实例&#xff08;HTML 模板&#xff09;4、React 安装二、React 核心1、JSX2、元素渲染3、组件4、Props5、State6、组件生命周期7、事件处理8、条件渲染9、列表 & Key10、表单11、状态提升12、组…

dropMenu----简单的下拉菜单生成器

HTML <div class"input-group"><span class"input-group-addon" style"width: 100px" >职级&#xff1a;</span><input type"text" class"units form-control" id"jobTitle" value"其…

Holedox Moving

2012-08-11 我的第一个A*算法&#xff1a; 四处看A*算法。。还是有一点没有弄明白就是那个当已经在列表中的时候再次进入的时候怎么去更新。 这道题。。有点难开始的时候不会位压缩&#xff0c;去看了一个别人的代码。所以感谢一下。这位高手。写了一个bfs(),500多ms。 看了A*…

mint mvc文件上传功能——使用篇

为什么80%的码农都做不了架构师&#xff1f;>>> 为了不打击大家的积极性&#xff0c;暂时只着重讲用法&#xff0c;原理方面暂时不讲太多。 配置web.xml 文件上传需要用到servlet3的异步处理功能。需要在web.xml配置文件中加入异步支持声明&#xff08;注释处&am…

TLS 1.2详解

TSL由多个协议组成的两层协议集合&#xff0c;工作与应用层和传输层之间。 TLS协议包含两层协议&#xff1a;记录层协议&#xff08;TLS Record Protocol协议&#xff09;和 握手协议&#xff08;TLS Handshake Protocol协议&#xff09;&#xff0c;底层采用可靠传输协议&…

个人作业2——英语学习APP案例分析

第一部分 调研&#xff0c; 评测 1.下载并使用&#xff0c;描述最简单直观的个人第一次上手体验&#xff1a; 没有各种广告&#xff0c;界面简洁&#xff0c;软件安装包略小于其他翻译软件。就内存的占用而言优于同款热门软件有道词典。 2.必应词典&#xff08;Android客户端&a…

IOS开发UI篇之──自定义加载等待框(MBProgressHUD)

这里介绍一下网友开源的MBProgressHUD类&#xff0c;实现等待框&#xff0c; 一、网上下载 MBProgessHUD 类文件&#xff0c;直接导入到工程即可 二、示例分析 在我的工程中示例如下&#xff1a; 1&#xff09;在ShowImageViewController.h头文件代码如下&#xff1a; #import…

java中跨时区的日期格式转换

2019独角兽企业重金招聘Python工程师标准>>> 先上一段代码 public class DataTransfer {public static void main(String[] args) {String dateStr "Sep 30, 2014 12:00:00 AM";SimpleDateFormat sdf new SimpleDateFormat();sdf.applyPattern("MM…

C语言读取写入CSV文件 [二]进阶篇——写入CSV文件

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 本系列文章目录 [一] 基础篇 [二] 进阶篇——写入CSV [三] 进阶篇——读取CSV 什么是 包裹&#xff08;使用双引号&…

K8S中部署apisix(非ingress)

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 不使用pvc的方式在K8S中部署apisix-gateway 简介 因为公司项目准备重构&#xff0c;现在做技术储备&#xff0c;之前公司项…

机器学习理论知识部分--偏差方差平衡(bias-variance tradeoff)

摘要&#xff1a; 1.常见问题 1.1 什么是偏差与方差&#xff1f; 1.2 为什么会产生过拟合&#xff0c;有哪些方法可以预防或克服过拟合&#xff1f; 2.模型选择例子 3.特征选择例子 4.特征工程与数据预处理例子 内容&#xff1a; 1.常见问题 1.1 什么是偏差与方差&#xff1f; …

有手就行3——持续集成环境—maven、tomcat、安装和配置

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 有手就行3——持续集成环境—maven、tomcat、安装 持续集成环境**(5)-Maven****安装和配置** 持续集成环境(6)-Tomcat安装…

.netcore基础知识(一)

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 先来说说web服务器 先来一张图 一个典型的进程外托管模型 我们先看kestrel这一部分 我们在它前面放了一个方向代理服务器n…

BZOJ 1791 岛屿(环套树+单调队列DP)

题目实际上是求环套树森林中每个环套树的直径。 对于环套树的直径&#xff0c;可以先找到这个环套树上面的环。然后把环上的每一点都到达的外向树上的最远距离作为这个点的权值。 那么直径一定就是从环上的某个点开始&#xff0c;某个点结束的。 把环拆成链&#xff0c;定义dp[…

什么是SAS

什么是SAS&#xff1f;简单的说&#xff0c;SAS是一种磁盘连接技术。它综合了现有并行SCSI和串行连接技术&#xff08;光纤通道、SSA、IEEE1394及InfiniBand等&#xff09;的优势&#xff0c;以串行通讯为协议基础架构&#xff0c;采用SCSI-3扩展指令集并兼容SATA设备&#xff…