参考文章
html
html5语义化和新特性
-
语义化让网页结构更加清晰,便于浏览器、搜索引擎的解析,便于阅读维护。
-
语义化标签
- Header main footer nav article aside detailes dialog
-
增强表单
- Color date email number range search url time tel month
-
新增的表单属性
- Placehoder required min max step autofoucs
-
视频和音频
- audio video
-
canvas/svg绘图
- canvas标签是图形容器,必须使用脚本来绘制图形
- svg是可伸缩的矢量图
-
地理位置
-
拖放Api
-
Web Worker
- 运行在后台的js脚本,独立于其他脚本,不影响页面性能
-
Web Storage
- localStorage: 没有时间限制的数据存储
- sessionStorage: 针对一个session的数据存储,当用户关闭浏览器窗口后数据会被删除
-
webSocket
浏览器的标准模式和怪异模式/怪异盒模型
- 现代浏览器一般都有两种渲染模式
标准模式和怪异模式, - 在标准模式下文档按照w3c的规范进行解析和渲染
- 在怪异模式下文档按照浏览器自己的方式进行解析和渲染
- 比如, 标准模式下盒模型的总宽度等于左右margin + 左右padding + 左右的border + 内容的宽度
- 而在怪异模式下内容的宽度包含了padding和border
- 在标准模式下可以使用box-sizing把盒模型变成怪异盒模型
html和xhtml的区别
- html是超文本标记语言,是一种语法比较松散的标记语言,语法要求也不严格。比如说,标签名大小写混写,属性名单双引号都可以,标签可以不闭合
- xhtml是可扩展的标记语言,可以说是html的严格模式,比如标签名必须小写,属性必须加引号,引号必须是双引号,标签要闭合等等。
使用data-*的好处
- data属性是h5新增的一种用于保存自定义数据的属性,总是以
data-出现。 - 好处是可以储存些不需要在浏览器上展示但又重要的信息,比如文章的ID
- 用js可以很方便的去读写这些属性,比如
datasetgetAttributesetAttributedata()
canvas
canavs是h5新增一种用户绘制图形的容器标签,通过js绘制,常用于动画,游戏页面,数据可视化。
定位的写法
position: absolute;/*绝对定位*/
position: relative; /*相对定位*/
position: flexd; /*固定定位* /
CSS/js放置的位置和原因
什么是渐进式渲染
指浏览器不用等待所有页面资源都渲染好之后再呈现给用户看,而是边下载边渲染,所以用户打开一个网页的时候往往不能第一时间看到所有的内容,但是能够看到一个大概的样子,后续的内容浏览器会慢慢补上形成一个完整的页面。
- 解决什么问题: 为了解决js加载时间的问题。
- 怎么实现 服务器端渲染SSR,流行的vue,react都有SSR解决方案。
模板引擎
是一种将业务逻辑层和表现层分离,将规定格式的模板代码转化为业务数据的实现
meta viewport原理
前端页面有哪三层构成,分别是什么?作用是什么?
- 结构层
- 由html/xhtml之类的标记语言来创建,对网页内容的语义做出描述
- 表示层
- 由css负责创建,对网页如何显示内容做出定义
- 行为层
- 有JavaScript负责。对网页内容应该如何对事件做出反应
网页验证码有什么作用。
- 区分是计算机程序还是人,防止恶意攻击
- 防止黑客针对某个特定用户以特定程序进行暴力破解
渐进增强和优雅降级
- 渐进增强一开始保证最基本的功能,再改进和追加功能
- 优雅降级一开始就构建完整的功能,在针对低版本浏览器进行兼容
区别
优雅降级从复杂开始,渐进增强则从一个基础的版本开始,并不断扩充。
DOM和BOM
- DOM是文档对象模型,是为了操作文档出现的API,document是其中的一个对象
- DOM是浏览器对象模型,是为了操作浏览器出现的API,window是其中的一个对象
css
解释一下"::before"和":after"中的双冒号和单冒号的区别
- 双冒号表示伪元素,单冒号表示伪类
页面导入样式时使用link和@import的区别?
-
Link 属于 html 标签,而@import 是 CSS 中提供的
-
在页面加载的时候,link会同时被加载,而@import引用的CSS会在页面加载完成后才会加载引用的CSS
-
@import只有在ie5以上才可以被识别,而link是html标签,不存在浏览器兼容性问题
-
link引入样式的权重大于@import的引用(@import 是将引用的样式导入到当前的页面中)
CSS 清除浮动的几种方法: clear:both;
-
在浮动的盒子下面再放一个标签,使用 clear:both;来清除浮动
-
使用
overflow清除浮动, 找到浮动盒子的父元素,给父元素添加overflow:hidden;属性 -
.clearfix:after { content:""; height:0; line-height:0; display:block; clear:both; visibility:hidden; } // 兼容IE6 .clearfix { zoom: 1; }
CSS 选择符有哪些?哪些属性可以继承?优先级算法如何计算? CSS3 新增伪类有那些?
1.id选择器( # myid)
2.类选择器(.myclassname)
3.标签选择器(div, h1, p)
4.相邻选择器(h1 + p)
5.子选择器(ul < li)
6.后代选择器(li a)
7.通配符选择器( * )
8.属性选择器(a[rel = "external"])
9.伪类选择器(a: hover, li: nth-child)
* 可继承: font-size font-family color, UL LI DL DD DT;
* 不可继承 :border padding margin width height ;
* 优先级就近原则,样式定义最近者为准;
* 载入样式以最后载入的定位为准;
优先级为:
!important > id > class > tag important 比 内联优先级高
CSS3新增伪类举例:
p:first-of-type 选择属于其父元素的首个 <p> 元素的每个 <p> 元素。
p:last-of-type 选择属于其父元素的最后 <p> 元素的每个 <p> 元素。
p:only-of-type 选择属于其父元素唯一的 <p> 元素的每个 <p> 元素。
p:only-child 选择属于其父元素的唯一子元素的每个 <p> 元素。
p:nth-child(2) 选择属于其父元素的第二个子元素的每个 <p> 元素。
:enabled、:disabled 控制表单控件的禁用状态。
:checked,单选框或复选框被选中。
行内元素和块级元素的具体区别是什么?行内元素的padding和margin可设置吗?
-
块级元素(block)特性:
- 总是独占一行,表现为另起一行开始,而且其后的元素也必须另起一行显示;
- 宽度(width)、高度(height)、内边距(padding)和外边距(margin)都可控制;
-
内联元素(inline)特性:
- 和相邻的内联元素在同一行;
- 宽度(width)、高度(height)、内边距的 top/bottom(padding-top/padding-bottom)和外边距的 top/bottom(margin-top/margin-bottom)都不可改变(也就是 padding 和 margin 的 left 和 right 是可以设置的),就是里面文字或图片的大小。
inline-block元素有哪些?
<input> 、<img> 、<button> 、<texterea> 、<label>。
如何垂直居中一个元素?
- 方法一:绝对定位居中(原始版之已知元素的高宽)
.content {
width: 200px;
height: 200px;
background-color: #6699ff;
position: absolute; /*父元素需要相对定位*/
top: 50%;
left: 50%;
margin-top: -100px; /*设为高度的1/2*/
margin-left: -100px; /*设为宽度的1/2*/
}
- 方法二:绝对定位居中(改进版之一未知元素的高宽)
.content {
width: 200px;
height: 200px;
background-color: #6699ff;
position: absolute; /*父元素需要相对定位*/
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /*在水平和垂直方向上各偏移-50%*/
}
- 方法三:绝对定位居中(改进版之二未知元素的高宽)
.content {
width: 200px;
height: 200px;
background-color: #6699ff;
margin: auto; /*很关键的一步*/
position: absolute; /*父元素需要相对定位*/
left: 0;
top: 0;
right: 0;
bottom: 0; /*让四个定位属性都为0*/
}
- 方法四:flex 布局居中
body {
display: flex; /*设置外层盒子display为flex*/
align-items: center; /*设置内层盒子的垂直居中*/
justify-content: center; /*设置内层盒子的水平居中*/
.content {
width: 200px;
height: 200px;
background-color: #6699ff;
}
}
垂直居中img
.content {
/* img的容器设置如下 */
display: table-cell;
text-align: center;
vertical-align: middle;
}
BFC
-
什么是 BFC
BFC(Block Formatting Context)格式化上下文,是 Web 页面中盒模型布局的 CSS 渲染模式,指一个独立的渲染区域或者说是一个隔离的独立容器。
-
形成 BFC 的条件
- 浮动元素,float 除 none 以外的值
- 定位元素,position(absolute,fixed)
- display 为以下其中之一的值 inline-block,table-cell,table-caption
- overflow 除了 visible 以外的值(hidden,auto,scroll)
-
BFC 的特性
- 内部的 Box 会在垂直方向上一个接一个的放置。
- 垂直方向上的距离由 margin 决定
- bfc 的区域不会与 float 的元素区域重叠。
- 计算 bfc 的高度时,浮动元素也参与计算
- bfc 就是页面上的一个独立容器,容器里面的子元素不会影响外面元素。
用纯CSS创建一个三角形的原理是什么?
span {
width: 0;
heigh: 0;
border-top: 40px solid transparent;
border-left: 40px solid transparent;
border-right: 40px solid transparent;
border-bottom: 40px solid #ff0000;
}
Sass、LESS 是什么?为什么要使用他们?
-
他们是CSS预处理器。是 CSS 上的一种抽象层。他们是一种特殊的语法/语言编译成 CSS。
-
为什么要使用它们?
- 结构清晰,便于扩展。
- 可以方便地屏蔽浏览器私有语法差异。这个不用多说,封装对浏览器语法差异的重复处理,减少无意义的机械劳动。
- 可以轻松实现多重继承。
- 完全兼容 CSS 代码,可以方便地应用到老项目中。LESS 只是在 CSS 语法上做了扩展,所以老的 CSS 代码也可以与 LESS 代码一同编译。
css sprite(精灵) 是什么,有什么优缺点
-
概念:
将多个小图片拼接到一个图片中。通过 background-position 和元素尺寸调节需要显示的背景图案。
-
优点:
- 减少 HTTP 请求数,极大地提高页面加载速度。
- 增加图片信息重复度,提高压缩比,减少图片大小。
- 更换风格方便,只需在一张或几张图片上修改颜色或样式即可实现。
-
缺点:
- 图片合并麻烦。
- 维护麻烦,修改一个图片可能需要从新布局整个图片,样式
移动端 1px 像素问题及解决办法
- 媒体查询利用设备像素比缩放,设置小数像素;
- 优点:简单,好理解
- 缺点:兼容性差,目前之余IOS8+才支持,在IOS7及其以下、安卓系统都是显示0px。
IOS8+下已经支持带小数的px值,media query 对应 devicePixelRatio 有个查询值 -webkit-min-device-pixel-ratio;
.border { border: 1px solid #999 }
@media screen and (-webkit-min-device-pixel-ratio: 2) {
.border { border: 0.5px solid #999 }
}
@media screen and (-webkit-min-device-pixel-ratio: 3) {
.border { border: 0.333333px solid #999 }
}
<body><div id="main" style="border: 1px solid #000000;"></div></body>
<script type="text/javascript">
if (window.devicePixelRatio && devicePixelRatio >= 2) {
var main = document.getElementById('main');
main.style.border = '.5px solid #000000';
}
</script>
- viewport + rem
- 利用viewport + rem + js 动态的修改页面的缩放比例
- 适合新项目,老项目可能要涉及到较多的改动。
<meta name="viewport" id="WebViewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
*var* viewport = document.querySelector("meta[name=viewport]")
if (window.devicePixelRatio == 1) {
viewport.setAttribute('content', 'width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no')
}
if (window.devicePixelRatio == 2) {
viewport.setAttribute('content', 'width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no')
}
if (window.devicePixelRatio == 3) {
viewport.setAttribute('content', 'width=device-width, initial-scale=0.333333333, maximum-scale=0.333333333, minimum-scale=0.333333333, user-scalable=no')
}
*var* docEl = document.documentElement;
*var* fontsize = 10 * (docEl.clientWidth / 320) + 'px';
docEl.style.fontSize = fontsize;
- box-shadow
- 利用阴影也可以实现,优点是没有圆角问题,缺点是颜色不好控制
div {
-webkit-box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.5);
}
- 媒体查询 + transfrom(推荐)
/* 2倍屏 */
@media only screen and (-webkit-min-device-pixel-ratio: 2.0) {
.border-bottom::after {
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
}
/* 3倍屏 */
@media only screen and (-webkit-min-device-pixel-ratio: 3.0) {
.border-bottom::after {
-webkit-transform: scaleY(0.33);
transform: scaleY(0.33);
}
}
- transform: scale(0.5) 方案 - 推荐: 很灵活
div {
height:1px;
background:#000;
-webkit-transform: scaleY(0.5);
-webkit-transform-origin:0 0;
overflow: hidden;
}
- 其他参考
常见的CSS布局
-
单列布局
-
两列自适应布局
-
圣飞布局和双飞翼布局
-
flxd布局
CSS3 弹性盒子模型
-
弹性盒子是 CSS3 的一种新的布局模式。
-
CSS3 弹性盒( Flexible Box 或 flexbox),是一种当页面需要适应不同的屏幕大小以及设备类型时确保元素拥有恰当的行为的布局方式。
-
引入弹性盒布局模型的目的是提供一种更加有效的方式来对一个容器中的子元素进行排列、对齐和分配空白空间。
base64:
base64 是网络上最常见的用于传输 8Bit 字节代码的编码方式之一,要求把每三个 8Bit 的字节转换为四个 6Bit 的字节,Base64 是网络上最常见的用于传输 8Bit 字节代码的编码方式之一。
通俗点讲:将资源原本二进制形式转成以 64 个字符基本单位,所组成的一串字符串。
比如一张图片转成 base64 编码后就像这样,图片直接以 base64 形式嵌入文件中(很长没截完):
- 生成 base64 编码:
图片生成 base64 可以用一些工具,如在线工具,但在项目中这样一个图片这样生成是挺繁琐。
特别说下,webpack 中的 url-loader 可以完成这个工作,可以对限制大小的图片进行 base64 的转换,非常方便。
- 优点:
base64 的图片会随着 html 或者 css 一起下载到浏览器,减少了请求.
可避免跨域问题
- 缺点:
老东西(低版本)的 IE 浏览器不兼容。
体积会比原来的图片大一点。
css 中过多使用 base64 图片会使得 css 过大,不利于 css 的加载。
- 适用场景:
应用于小的图片几 k 的,太大的图片会转换后的大小太大,得不偿失。
用于一些 css sprites 不利处理的小图片,如一些可以通过 background-repeat 平铺来做成背景的图片
对偏移、卷曲(卷起)、可视的理解
偏移
offsetWidth width + padding + border
offsetHeight height + padding + border
offsetLeft
offsetTop
offsetParent
注意:没有offsetRight和offsetBottom
************************************************************************************************
卷曲
scrollWidth width + padding
scrollHeight 当内部的内容溢出盒子的时候, 顶边框的底部,计算到内容的底部;如果内容没有溢出盒子,计算方式为盒子内部的真实高度(边框到边框)
scrollLeft 这个scroll系列属性不是只读的
scrollTop
scroll()
此函数可以获取卷曲的高度和卷曲的宽度
function myScroll() {
return {
top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0,
left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0
};
}
滚动滚动条的时候触发事件
box(window).onscroll = function () {}
************************************************************************************************
可视
clientWidth 获取的是元素内部的真实宽度 width + padding
clientHeight 边框之间的高度
clientLeft 相当于左边框的宽度 如果元素包含了滚动条,并且滚动条显示在元素的左侧。这时,clientLeft属性会包含滚动条的宽度17px
clientTop 相当于顶边框的宽度
client()
此函数可以获取浏览器可视区域的宽高
function myClient() {
return {
wid: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 0,
heit: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 0
};
}
\----------------------------------------------------------------------------------------------
@offsetHeight和style.height的区别
demo.style.height只能获取行内样式,如果样式写到了其他地方,甚至根本就没写,便无法获取
style.height是字符串(而且带单位),offsetHeight是数值
demo.style.height可以设置行内样式,offsetHeight是只读属性
因此,一般用demo.offsetHeight来获取某元素的真实宽度/高度,用style.height来设置宽度/高度
\----------------------------------------------------------------------------------------------
@offsetLeft和style.left的区别
一、style.left只能获取行内样式
二、offsetLeft只读,style.left可读可写
三、offsetLeft是数值,style.left是字符串并且有单位px
四、如果没有加定位,style.left获取的数值可能是无效的
五、最大区别在于offsetLeft以border左上角为基准,style.left以margin左上角为基准
\----------------------------------------------------------------------------------------------
@scrollHeight和scrollWidth
标签内部实际内容的高度/宽度
不计算边框,如果内容不超出盒子,值为盒子的宽高(不带边框)
如果内容超出了盒子,就是从顶部或左部边框内侧一直到内容a的最外部分
\----------------------------------------------------------------------------------------------
@scrollTop和scrollLeft
被卷去部分的 顶部/左侧 到可视区域 顶部/左侧 的距离
如何解决不同浏览器的样式兼容性问题?
-
在确定问题原因和有问题的浏览器后,使用单独的样式表,仅供出现问题的浏览器加载。这种方法需要使用服务器端渲染。
-
使用已经处理好此类问题的库,比如 Bootstrap。
-
使用
autoprefixer自动生成 CSS 属性前缀。 -
使用 Reset CSS 或 Normalize.css。
如何为功能受限的浏览器提供页面? 使用什么样的技术和流程?
-
优雅的降级:为现代浏览器构建应用,同时确保它在旧版浏览器中正常运行。
-
渐进式增强:构建基于用户体验的应用,但在浏览器支持时添加新增功能。
-
利用 caniuse.com 检查特性支持。
-
使用
autoprefixer自动生成 CSS 属性前缀。 -
使用 Modernizr进行特性检测。
{box-sizing: border-box;}会产生怎样的效果?
-
元素默认应用了
box-sizing: content-box,元素的宽高只会决定内容(content)的大小。 -
box-sizing: border-box改变计算元素width和height的方式,border和padding的大小也将计算在内。 -
元素的
height= 内容(content)的高度 + 垂直方向的padding+ 垂直方向border的宽度 -
元素的
width= 内容(content)的宽度 + 水平方向的padding+ 水平方向border的宽度
文本超出部分显示省略号
- 单行
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
- 多行
display: -webkit-box;
-webkit-box-orient: vertical;
/* 最多显示几行*/
-webkit-line-clamp: 3;
overflow: hidden;
JavaScript
订阅发布
观察者
cmd commonjs amd
列举 3 种强制类型转换和 2 种隐式类型转换
- 强制(parseInt,parseFloat,Number)、隐式(+ -)
js基本数据类型、引用数据类型,null、undefined的区别
- 基本数据类型有:String、Number、Boolean、Symbol、Null、Undefined
- 引用类型:Object、Array、Function
- null是空对象,undefined是未定义的值
判断Array类型、Number类型
- typeof
- instanceof
- Object.prototype.toString.call()
- Array.isArray
Object是引用类型吗?引用类型和基本类型的区别?哪个在堆哪个在栈
- Object是引用类型
- 引用类型是按引用范文,基本类型是按值访问。
- 引用类型在栈里保存一个十六进制的空间地址,指向堆内存中的一个对象,基本数据类型存储在栈中
DOM操作Api
- Document.getElementById, ByTagName、ByClassName\querySelector\querySelectAll
- createElement\ createTextNode\ cloneNode\ createDocumentFragment
- appendChild\insertChild\removeChild\replaceChild
- setAttribute\getAttribute\dataset
this的使用场景
- 函数有所属对象指向所属对象,没有则指向全局对象
- new 一个对象时指向这个对象
- call\apply\bind 改变this的指向
创建对象的方式
- 工厂模式
function Person(name) {
var o = new Object;
o.age = name;
o.sayName = function(){
console.log(this.name);
}
return o;
}
// 缺点:对象无法识别
- 构造函数模式
function person(name) {
this.name = name;
// 优化点:可以把函数放到外面,这样每次创建实例的时候函数就不会被重新创建
this.sayName = function() {
console.log(this.name)
}
}
// 优点:可以识别对象的类型
// 缺点:每次创建实例函数都要重新创建一遍
- 原型模式
function Person() {}
person.prototype.name = 'nick';
person.prototype.sayName = function(){
console.log(this.name);
}
var person1 = new Person();
// 优点:方法和属性都共享
// 缺点:不能初始化参数
// 优化版
function Person(name) {}
Person.prototype = {
constructor: Person,
name: 'nick',
getName: function () {
console.log(this.name);
}
};
var person1 = new Person();
// 可以通过constructor找到所属构造函数
- 组合模式
function Person(name) {
this.name = name;
}
person.prototype = function() {
console.log(this.name);
}
const person = new Person('nick');
// 优点:方法共享,变量私有。
- 动态原型模式
function Person(name) {
this.name = name;
if (typeof this.sayName != 'function') {
Person.prototype.sayName = function() {
console.log(this.name);
}
}
}
- 寄生构造函数模式
function Person(name) {
var o = new Object;
o.age = name;
o.sayName = function(){
console.log(this.name);
}
return o;
}
// 示例
function SpecialArray() {
var values = new Array();
for (var i = 0, len = arguments.length; i < len; i++) {
values.push(arguments[i]);
}
values.toPipedString = function () {
return this.join("|");
};
return values;
}
var colors = new SpecialArray('red', 'blue', 'green');
var colors2 = SpecialArray('red2', 'blue2', 'green2');
console.log(colors);
console.log(colors.toPipedString()); // red|blue|green
console.log(colors2);
console.log(colors2.toPipedString()); // red2|blue2|green2
// 你会发现,其实所谓的寄生构造函数模式就是比工厂模式在创建对象的时候,多使用了一个new,实际上两者的结果是一样的。
- 稳妥构造函数模式
function Person(name) {
var o = new Object;
o.sayName = function(){
console.log(this.name);
}
return o;
}
// 没有公共属性
new 操作符
- 1、创建一个空的新对象,(如:var car1 = {});
- 2、新对象的
__proto__属性指向构造函数的原型对象。(原型.prototype) - 3、将构造函数的作用域赋值给新对象。(即this指向这个新对象)
- 4、执行构造函数内部的代码,为这个新对象添加属性。
- 5、如果该函数没有返回对象,则隐式的返回this。
// 定义构造函数
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
var car1 = new Car('Eagle', 'Talon TSi', 1993);
console.log(car1.make);
// expected output: "Eagle"
什么是“use strict”, 好处和坏处
变量必须先声明,再使用
不能对变量执行delete操作
对象的属性名不能重复
禁用eval()
函数的arguments参数
禁用with(){}
变量提升
- 在当前作用域下,代码运行之前,把带var的和带function进行提前声明,
- 带var的只声明,不定义(不赋值),带function声明加定义。
- 函数定义三步:
- 开辟一个堆内存,会有一个十六进制的空间地址(指正)
- 把函数体中的代码当做字符串存储进行
- 把空间地址(指针)赋值给函数名;
- 特殊情况
- 不管条件是否成立,都要进行变量提升
- function在新的浏览器中只声明不定义,旧浏览器声明加定义
- 变量提升只发生在等号左边
- return之后代码不执行,但要进行变量提升
- 变量名重复,不需要重复声明但要重新定义
- 匿名函数不进行变量提升
- 不管条件是否成立,都要进行变量提升
作用域
全局作用域:在代码中任何地方都能访问到的对象拥有全局作用域。- 拥有全局作用域的有
window对象的属性定义在最外层的函数或变量未定义直接赋值的变量
- 拥有全局作用域的有
局部作用域:一般在代码片段内可以访问到的,最常见的如函数内部,所以又称函数作用域块级作用域: 在任何一对{}中的定义的变量在代码块外都不可见。
堆栈内存
- 栈是一种遵循先进后出原则的有序集合,主要用来提供JavaScript的运行环境和存储基本数据类型值
- 堆的数据结构是一种树状结构,特点是存储的键值对(key-value)可以是无序的,主要用来存储引用数据类型值
- 栈内存释放
- 一般情况下,当函数执行完所形成的私有作用域会被自动释放掉,但也有特殊不销毁的情况
- 函数内的某些内容被外部变量占用了。(闭包)
- 全局栈内存只有在页面关闭的时候才会被释放
- 一般情况下,当函数执行完所形成的私有作用域会被自动释放掉,但也有特殊不销毁的情况
- 堆内存释放
- 让引用堆内存空间地址的变量赋值为null.
const ary = [1,2,3,4,5,6];
Math.max(1,2,3,4,5,6);
Math.max.call()
1 === '1'
1== '1'
[1] == [2]
闭包的理解?
- 三个特性
- 函数嵌套函数
- 内部函数可以访问外部函数的参数和变量
- 参数和变量不会被垃圾机制回收
- 优点
- 变量长期保存在内存中
- 避免全局变量污染
- 缺点
- 常驻内存,增加内存使用量
- 使用不当会造成内存泄漏
实现跨域有多少种方式
jsonp
- Jsonp原理利用
script标签没有跨域限制的漏洞,从其他来源动态获取数据,jsonp请求需要服务器支持才可以,jsonp属于非同源 - jsonp的优点是简单、兼容性好,缺点是只支持
get请求,但可能会遭受xss攻击 - jsonp实现的方式是通过将前端方法以参数的形式传递给服务端,服务端注入参数再返回,实现服务端向客户端通信
cors跨资源共享
- CORS是一个
W3C标准,全称是跨域资源共享,允许浏览器向跨域服务器发送XMLHttpRequest/Fetch请求。 - 实现通信的关键是服务器,只要服务器实现了CORS接口,就可以跨域通信。
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
res.header("Content-Type", "application/json;charset=utf-8");
node中间件代理/webpack的devServer代理
- 接受客户端请求 => 将请求转发给服务器 => 拿到服务器的响应数据 => 将响应数据转发给客户端
nginx反向代理
- 和Node代理一样的原理,只需要修改nginx的配置项
websocket
Websocket是一种双向通信协议,建立连接之后,server端和client端都能主动向对方接收/发送数据。
postMessage
- 主要解决页面和新窗口的数据传递,多窗口的数据传递,页面和嵌套的iframe消息传递
window.name + iframe,localtion.hash + iframe,document.domain + iframe
了解过,没用过。
浅拷贝和深拷贝区别
-
浅拷贝
- 浅拷贝会创建了一个新对象,这个对象有原始对象属性值的一份精确拷贝。属性值如果是基本类型则拷贝变量的值,引用类型则拷贝内存地址,如果其中一个对象改变了则会影响另一个对象。
- 常使用方法
Object.assign():将所有可枚举的值从一个或多个源对象复制到目标对象- ES6的展开运算符
Array.prototype.slice()返回数组的浅拷贝
-
深拷贝
- 深拷贝会拷贝基本类型的值并拷贝引用类型指向的对象。深拷贝相对于浅拷贝速度较慢并且花销较大,但拷贝后两个对象互补不影响
- 可以用
JSON.parse(JSON.stringify(object))进行深拷贝,但会有几个问题,比如不能处理正则,不能处理new Date,不能序列化函数,会忽略undefined和symbol
-
总结:
- 如果基本数据,浅拷贝和深拷贝都不会改变原数据
- 如果是引用类型,浅拷贝的对象改变会影响原对象,深拷贝则不会。
-
实现一个深拷贝
// 简单深拷贝
const cloneDeep = (source) => {
let target = Array.isArray(source) ? [] : {};
for(const key in source) {
const val = source[key];
if(typeof val === 'object') {
target[key] = cloneDeep(val);
} else {
target[key] = val;
}
}
return target;
}
// 增强版的深拷贝
const isObject = (object) => {
// 兼容数组
return typeof object === 'object' && object != null;
}
const cloneDeep = (source) => {
// 参数校验
if(!isObject(source)) return source;
let target = Array.isArray(source) ? [] : {};
// 哈希表解决循环引用
let hash = new WeakMap();
hash.set(source, target);
for (const key in source) {
// hasOwnProperty: 检测对象自身属性中是否具有指定的属性,返回布尔值
if (source.hasOwnProperty(key)) {
if(typeof source[key] === 'object') {
// 递归实现深拷贝
target[key] = cloneDeep(source[key]);
} else {
target[key] = source[key];
}
}
}
return target;
}
var a = {
name: "muyiy",
book: {
title: "You Don't Know JS",
price: "45",
demo: {
demo: {
demo: {}
},
demo1: {
demo: {}
},
demo2: {
demo: {}
}
}
},
a1: undefined,
a2: null,
a3: 123
}
var b = cloneDeep(a);
a.name = "高级前端进阶";
a.book.price = "55";
a.circleRef = a;
b.book.demo.demo.demo.a = 2;
console.log(a);
console.log(b);
原型链、手绘原型链
原型
- 函数都带有一个
prototype属性,这是属性是指向构造函数的原型对象,这个对象包含所有实例共享的属性和方法。 - 原型对象都有一个
constructor属性,这个属性指向所关联的构造函数。 - 每个对象都有一个
__proto__属性[非标准的方法],这个属性指向构造函数的原型prototype
原型链
- 当访问实例对象的某个属性时,会先在这个对象本身的属性上查找,如果没有找到,则会通过
__proto__属性去原型上查找,如果还没有找到则会在构造函数的原型的__proto__中查找,这样一层层向上查找就会形成一个作用域链,称为原型链
为什么要有原型链
- 构造函数的所有实例都可以访问构造函数原型上的属性和方法
- 继承,子类可以继承父类的方法
手绘原型图

事件冒泡/事件捕获/事件委托(事件代理)
- 事件冒泡
- 由里向外传播,直到document对象
- 事件捕获
- 由外向里传播直到最具体的元素.
- 事件委托/事件代理
- 事件委托/代理就是利用事件冒泡,只指定一个事件处理器就可以管理某一类型的所有事件。
- 优点:
- 减少大量的内存占用,事件注册。
- 动态新增元素无需再对其绑定事件
- 缺点:
- 如果所有事件都用委托/代理的话会出现本不应该绑定的事件被绑上了事件
- 阻止冒泡
event.stopPropagation()
//非标准,已废弃
event.cancelBubble=false; // IE
- 阻止事件的默认行为
event.preventDefault()
//非标准,已废弃
event.returnValue=false; //IE
手写Ajax
- promise Ajax
let getJson = url => {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) return;
if (xhr.readyState === 4 && 200 === xhr.status) {
resolve(xhr.responseText);
} else {
reject();
}
};
xhr.send();
});
};
getJson('/login').then(function (res) {
return getJson('/order');
}).then(function(res){
console.log(res);
}).catch(function (err) {
console.log('error')
})
- 普通函数
let xhr = new XMLHttpRequest();
xhr.open('get', 'http://xxx.com');
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) return;
if (200 === xhr.status) {
console.log(xhr.responseText)
}
};
xhr.send();
防抖、节流
防抖(debounce)
指的是某个函数在某段时间内,无论触发了多少次回调,都只执行最后一次。
-
实现方案 使用定时器,函数第一次执行时设定一个定时器,之后调用时发现已经设定过定时器就清空之前的定时器,并重新设定一个新的定时器,如果存在没有被清空的定时器,当定时器计时结束后触发函数执行
-
示例
/**
* 延迟执行
* fn: 需要防抖的函数
* wait: 时间间隔
* */
function debounce(fn, wait = 50) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, wait)
}
}
/**
* 立即执行
* fn: 需要防抖的函数
* wait: 时间间隔
* */
function debounce(fn, wait = 50) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
let callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait);
if (callNow) fn.apply(this, args);
}
}
节流(throttle)
函数节流指的是某个函数在一定时间间隔内只执行一次,在这间隔内无视后来产生的函数调用请求
-
实现方案
-
每次执行时设定一个上次执行的时间戳,判断是否已到执行时间,如果到了就执行。
-
设定一个定时器,如果定时器存在则直接返回,等待异步执行完毕清空定时器。
-
-
示例
// 时间戳
const throttle = (fn, wait = 50) => {
// 上一次执行 fn 的时间
let previous = 0;
// 将 throttle 处理结果当作函数返回
return function(...args) {
// 获取当前时间,转换成时间戳,单位毫秒
let now = +new Date();
// 将当前时间和上一次执行函数的时间进行对比
// 大于等待时间就把 previous 设置为当前时间并执行函数 fn
if (now - previous > wait) {
previous = now
fn.apply(this, args)
}
}
}
// 定时器版本
const throttle = function(fn, wait) {
let timer = null;
return function(...args) {
if (timer) return;
timer = setTimeout(() => {
timer = null;
fn.apply(this, args);
}, wait);
};
}
数组去重
const ary1 = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4];
const ary2 = ary1.filter((element, index, ary) => {
// 判断索引是否相等
return ary.indexOf(element) === index;
}) // => [1, 2, 3, 5, 4]
const ary3 = [...new Set(arr1)]
// => [1, 2, 3, 5, 4]
const ary4 = Array.from(new Set(arr1))
// => [1, 2, 3, 5, 4]
const ary5 = arr1.reduce((prev, next) => {
return prev.includes(next) ? prev : [...prev, next];
}, []);
new 一个对象具体做了什么
- 1、创建一个空的新对象,(如:var car1 = {});
- 2、新对象的
_proto_属性指向构造函数的原型对象。(原型.prototype) - 3、将构造函数的作用域赋值给新对象。(即this指向这个新对象)
- 4、执行构造函数内部的代码,为这个新对象添加属性。
- 5、如果该函数没有返回对象,则隐式的返回this。
// 定义构造函数
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
var car1 = new Car('Eagle', 'Talon TSi', 1993);
console.log(car1.make);
// expected output: "Eagle"
call\apply\bind
call:使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
fun.call(thisArg[,arg1[,arg2[, ...]]])
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
}
console.log(new Food('cheese', 5).name); //=>"cheese"
apply: 调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数
fun.apply(thisArg, argsArray)
var numbers = [5, 6, 2, 3, 7];
// 最大值
var max = Math.max.apply(null, numbers);
console.log(max); //=> 7
// 最小值
var min = Math.min.apply(null, numbers);
console.log(min); //=> 2
bind:创建一个新的函数,在bind被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。
function.bind(thisArg[,arg1[,arg2[, ...]]])
var module = {
x: 42,
getX: function() {
return this.x;
}
}
var unboundGetX = module.getX;
console.log(unboundGetX()); // => undefined
var boundGetX = unboundGetX.bind(module);
console.log(boundGetX()); // => 42
数组API
Array.from()
- 从一个类似数组或可迭代对象中创建一个新的,浅拷贝的数组实例。
console.log(Array.from('foo'));
// expected output: Array ["f", "o", "o"]
console.log(Array.from([1, 2, 3], x => x + x));
// expected output: Array [2, 4, 6]
Array.isArray()
- 用于确定传递的值是否是一个 Array。
Array.isArray([1, 2, 3]);
// true
Array.isArray({foo: 123});
// false
Array.isArray("foobar");
// false
Array.isArray(undefined);
// false
Array.prototype.concat()
- 合并两个或多个数组。此方法不会更改现有数组,返回新数组。
const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
console.log(array1.concat(array2));
// expected output: Array ["a", "b", "c", "d", "e", "f"]
Array.prototype.every()
- 测试一个数组内的所有元素是否都能通过某个指定函数的测试。返回布尔值。
var array1 = [1, 30, 39, 29, 10, 13];
function isBelowThreshold(currentValue) {
return currentValue < 40;
}
console.log(array1.every(isBelowThreshold));
// expected output: true
Array.prototype.filter()
- 创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
var words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
const result = words.filter(word => word.length > 6);
console.log(result);
// expected output: Array ["exuberant", "destruction", "present"]
Array.prototype.some()
- 测试数组中是不是有元素通过了被提供的函数测试。它返回的是一个Boolean类型的值。
var array = [1, 2, 3, 4, 5];
var even = function(element) {
// checks whether an element is even
return element % 2 === 0;
};
console.log(array.some(even));
// expected output: true
Array.prototype.find()
- 返回数组中满足提供的测试函数的第一个元素的值。否则返回
undefined。
var array1 = [5, 12, 8, 130, 44];
var found = array1.find(function(element) {
return element > 10;
});
console.log(found);
// expected output: 12
Array.prototype.findIndex()
- 返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。
var array1 = [5, 12, 8, 130, 44];
function isLargeNumber(element) {
return element > 13;
}
console.log(array1.findIndex(isLargeNumber));
// expected output: 3
Array.prototype.forEach()
- 对数组的每个元素执行一次提供的函数。
var array1 = ['a', 'b', 'c'];
array1.forEach(function(element) {
console.log(element);
});
// expected output: "a"
// expected output: "b"
// expected output: "c"
Array.prototype.map()
- 创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
var array1 = [1, 4, 9, 16];
// pass a function to map
const map1 = array1.map(x => x * 2);
console.log(map1);
// expected output: Array [2, 8, 18, 32]
Array.prototype.includes()
- 用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。
var array1 = [1, 2, 3];
console.log(array1.includes(2));
// expected output: true
var pets = ['cat', 'dog', 'bat'];
console.log(pets.includes('cat'));
// expected output: true
console.log(pets.includes('at'));
// expected output: false
Array.prototype.indexOf()
- 返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
var beasts = ['ant', 'bison', 'camel', 'duck', 'bison'];
console.log(beasts.indexOf('bison'));
// expected output: 1
// start from index 2
console.log(beasts.indexOf('bison', 2));
// expected output: 4
console.log(beasts.indexOf('giraffe'));
// expected output: -1
Array.prototype.lastIndexOf()
- 返回指定元素(也即有效的 JavaScript 值或变量)在数组中的最后一个的索引,如果不存在则返回 -1。从数组的后面向前查找,从 fromIndex 处开始。
var animals = ['Dodo', 'Tiger', 'Penguin', 'Dodo'];
console.log(animals.lastIndexOf('Dodo'));
// expected output: 3
console.log(animals.lastIndexOf('Tiger'));
// expected output: 1
Array.prototype.join()
- 将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。如果数组只有一个项目,那么将返回该项目而不使用分隔符。
var elements = ['Fire', 'Air', 'Water'];
console.log(elements.join());
// expected output: "Fire,Air,Water"
console.log(elements.join(''));
// expected output: "FireAirWater"
console.log(elements.join('-'));
// expected output: "Fire-Air-Water"
Array.prototype.flat()
- 按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
var arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]
var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]
var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]
//使用 Infinity 作为深度,展开任意深度的嵌套数组
arr3.flat(Infinity);
// [1, 2, 3, 4, 5, 6]
var arr4 = [1, 2, , 4, 5];
arr4.flat();
// [1, 2, 4, 5]
Array.prototype.flatMap()
var arr1 = [1, 2, 3, 4];
arr1.map(x => [x * 2]);
// [[2], [4], [6], [8]]
arr1.flatMap(x => [x * 2]);
// [2, 4, 6, 8]
// 只会将 flatMap 中的函数返回的数组 “压平” 一层
arr1.flatMap(x => [[x * 2]]);
// [[2], [4], [6], [8]]
let arr = ["今天天气不错", "", "早上好"]
arr.map(s => s.split(""))
// [["今", "天", "天", "气", "不", "错"],[],["早", "上", "好"]]
arr.flatMap(s => s.split(''));
// ["今", "天", "天", "气", "不", "错", "早", "上", "好"]
Array.prototype.keys()
- 返回一个包含数组中每个索引键的Array Iterator对象。
var array1 = ['a', 'b', 'c'];
var iterator = array1.keys();
for (let key of iterator) {
console.log(key); // expected output: 0 1 2
}
Array.prototype.values()
- 返回一个新的Array Iterator对象,该对象包含数组每个索引的值
const array1 = ['a', 'b', 'c'];
const iterator = array1.values();
for (const value of iterator) {
console.log(value); // expected output: "a" "b" "c"
}
Array.prototype.pop()
- 从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。
var plants = ['broccoli', 'cauliflower', 'cabbage', 'kale', 'tomato'];
console.log(plants.pop());
// expected output: "tomato"
console.log(plants);
// expected output: Array ["broccoli", "cauliflower", "cabbage", "kale"]
plants.pop();
console.log(plants);
// expected output: Array ["broccoli", "cauliflower", "cabbage"]
Array.prototype.push()
- 将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
const animals = ['pigs', 'goats', 'sheep'];
const count = animals.push('cows');
console.log(count);
// expected output: 4
console.log(animals);
// expected output: Array ["pigs", "goats", "sheep", "cows"]
animals.push('chickens', 'cats', 'dogs');
console.log(animals);
// expected output: Array ["pigs", "goats", "sheep", "cows", "chickens", "cats", "dogs"]
Array.prototype.shift()
- 从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。
var array1 = [1, 2, 3];
var firstElement = array1.shift();
console.log(array1);
// expected output: Array [2, 3]
console.log(firstElement);
// expected output: 1
Array.prototype.unshift()
- 将一个或多个元素添加到数组的开头,并返回该数组的新长度(该方法修改原有数组)。
var array1 = [1, 2, 3];
console.log(array1.unshift(4, 5));
// expected output: 5
console.log(array1);
// expected output: Array [4, 5, 1, 2, 3]
Array.prototype.reduce()
- 对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15
Array.prototype.reduceRight()
- 接受一个函数作为累加器(accumulator)和数组的每个值(从右到左)将其减少为单个值。
const array1 = [[0, 1], [2, 3], [4, 5]].reduceRight(
(accumulator, currentValue) => accumulator.concat(currentValue)
);
console.log(array1);
// expected output: Array [4, 5, 2, 3, 0, 1]
Array.prototype.reverse()
- 将数组中元素的位置颠倒,并返回该数组。该方法会改变原数组。
var array1 = ['one', 'two', 'three'];
console.log('array1: ', array1);
// expected output: Array ['one', 'two', 'three']
var reversed = array1.reverse();
console.log('reversed: ', reversed);
// expected output: Array ['three', 'two', 'one']
/* Careful: reverse is destructive. It also changes
the original array */
console.log('array1: ', array1);
// expected output: Array ['three', 'two', 'one']
Array.prototype.slice()
- 返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。
var animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
console.log(animals.slice(2));
// expected output: Array ["camel", "duck", "elephant"]
console.log(animals.slice(2, 4));
// expected output: Array ["camel", "duck"]
console.log(animals.slice(1, 5));
// expected output: Array ["bison", "camel", "duck", "elephant"]
Array.prototype.splice()
- 通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组
var months = ['Jan', 'March', 'April', 'June'];
months.splice(1, 0, 'Feb');
// inserts at index 1
console.log(months);
// expected output: Array ['Jan', 'Feb', 'March', 'April', 'June']
months.splice(4, 1, 'May');
// replaces 1 element at index 4
console.log(months);
// expected output: Array ['Jan', 'Feb', 'March', 'April', 'May']
Array.prototype.sort()
- 用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的
var months = ['March', 'Jan', 'Feb', 'Dec'];
months.sort();
console.log(months);
// expected output: Array ["Dec", "Feb", "Jan", "March"]
var array1 = [1, 30, 4, 21, 100000];
array1.sort();
console.log(array1);
// expected output: Array [1, 100000, 21, 30, 4]
Array.prototype.toLocaleString()
- 返回一个字符串表示数组中的元素。数组中的元素将使用各自的 toLocaleString 方法转成字符串,这些字符串将使用一个特定语言环境的字符串(例如一个逗号 ",")隔开。
var array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')];
var localeString = array1.toLocaleString('en', {timeZone: "UTC"});
console.log(localeString);
// expected output: "1,a,12/21/1997, 2:12:00 PM",
// This assumes "en" locale and UTC timezone - your results may vary
Array.prototype.toString()
- 返回一个字符串,表示指定的数组及其元素。
var array1 = [1, 2, 'a', '1a'];
console.log(array1.toString());
// expected output: "1,2,a,1a"
字符串API
String.fromCharCode()
- String.fromCharCode(num1, ..., numN)
- 返回由指定的UTF-16代码单元序列创建的字符串。
console.log(String.fromCharCode(189, 43, 190, 61));
// expected output: "½+¾="
String.fromCodePoint()
- 返回使用指定的代码点序列创建的字符串。
console.log(String.fromCodePoint(9731, 9733, 9842, 0x2F804));
// expected output: "☃★♲你"
String.prototype.charAt()
- 从一个字符串中返回指定的字符。
var anyString = "Brave new world";
console.log("The character at index 0 is '" + anyString.charAt(0) + "'");
console.log("The character at index 1 is '" + anyString.charAt(1) + "'");
console.log("The character at index 2 is '" + anyString.charAt(2) + "'");
console.log("The character at index 3 is '" + anyString.charAt(3) + "'");
console.log("The character at index 4 is '" + anyString.charAt(4) + "'");
console.log("The character at index 999 is '" + anyString.charAt(999) + "'");
String.prototype.charCodeAt()
- 返回0到65535之间的整数,表示给定索引处的UTF-16代码单元 (在 Unicode 编码单元表示一个单一的 UTF-16 编码单元的情况下,UTF-16 编码单元匹配 Unicode 编码单元。但在——例如 Unicode 编码单元 > 0x10000 的这种——不能被一个 UTF-16 编码单元单独表示的情况下,只能匹配 Unicode 代理对的第一个编码单元) 。如果你想要整个代码点的值,使用 codePointAt()。
var sentence = 'The quick brown fox jumps over the lazy dog.';
var index = 4;
console.log('The character code ' + sentence.charCodeAt(index) + ' is equal to ' + sentence.charAt(index));
// expected output: "The character code 113 is equal to q"
String.prototype.codePointAt()
- 返回 一个 Unicode 编码点值的非负整数。
'ABC'.codePointAt(1); // 66
'\uD800\uDC00'.codePointAt(0); // 65536
'XYZ'.codePointAt(42); // undefined
String.prototype.concat()
- 将一个或多个字符串与原字符串连接合并,形成一个新的字符串并返回。
var hello = "Hello, ";
console.log(hello.concat("Kevin", " have a nice day.")); /* Hello, Kevin have a nice day. */
String.prototype.startsWith()
- 用来判断当前字符串是否以另外一个给定的子字符串开头,并根据判断结果返回 true 或 false。
const str1 = 'Saturday night plans';
console.log(str1.startsWith('Sat'));
// expected output: true
console.log(str1.startsWith('Sat', 3));
// expected output: false
String.prototype.endsWith()
- 用来判断当前字符串是否是以另外一个给定的子字符串“结尾”的,根据判断结果返回 true 或 false。
const str1 = 'Cats are the best!';
console.log(str1.endsWith('best', 17));
// expected output: true
const str2 = 'Is this a question';
console.log(str2.endsWith('?'));
// expected output: false
String.prototype.includes()
- 用于判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false。
var str = 'To be, or not to be, that is the question.';
console.log(str.includes('To be')); // true
console.log(str.includes('question')); // true
console.log(str.includes('nonexistent')); // false
console.log(str.includes('To be', 1)); // false
console.log(str.includes('TO BE')); // false
String.prototype.indexOf()
- 返回调用它的 String 对象中第一次出现的指定值的索引,从 fromIndex 处进行搜索。如果未找到该值,则返回 -1。
var paragraph = 'The quick brown fox jumps over the lazy dog. If the dog barked, was it really lazy?';
var searchTerm = 'dog';
var indexOfFirst = paragraph.indexOf(searchTerm);
console.log('The index of the first "' + searchTerm + '" from the beginning is ' + indexOfFirst);
// expected output: "The index of the first "dog" from the beginning is 40"
console.log('The index of the 2nd "' + searchTerm + '" is ' + paragraph.indexOf(searchTerm, (indexOfFirst + 1)));
// expected output: "The index of the 2nd "dog" is 52"
String.prototype.lastIndexOf()
- 返回指定值在调用该方法的字符串中最后出现的位置,如果没找到则返回 -1。length为需要检索字符串的长度,默认值为str.length。
"canal".lastIndexOf("a") // returns 3
"canal".lastIndexOf("a",2) // returns 1
"canal".lastIndexOf("a",0) // returns -1
"canal".lastIndexOf("x") // returns -1
// 区分大小写
"Blue Whale, Killer Whale".lastIndexOf("blue"); // returns -1
String.prototype.localeCompare()
- 返回一个数字来指示一个参考字符串是否在排序顺序前面或之后或与给定字符串相同。
// The letter "a" is before "c" yielding a negative value
'a'.localeCompare('c');
// -2 or -1 (or some other negative value)
// Alphabetically the word "check" comes after "against" yielding a positive value
'check'.localeCompare('against');
// 2 or 1 (or some other positive value)
// "a" and "a" are equivalent yielding a neutral value of zero
'a'.localeCompare('a');
// 0
String.prototype.match()
- 检索返回一个字符串匹配正则表达式的的结果。
var str = 'For more information, see Chapter 3.4.5.1';
var re = /see (chapter \d+(\.\d)*)/i;
var found = str.match(re);
console.log(found);
// logs [ 'see Chapter 3.4.5.1',
// 'Chapter 3.4.5.1',
// '.1',
// index: 22,
// input: 'For more information, see Chapter 3.4.5.1' ]
// 'see Chapter 3.4.5.1' 是整个匹配。
// 'Chapter 3.4.5.1' 被'(chapter \d+(\.\d)*)'捕获。
// '.1' 是被'(\.\d)'捕获的最后一个值。
// 'index' 属性(22) 是整个匹配从零开始的索引。
// 'input' 属性是被解析的原始字符串。
var str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
var regexp = /[A-E]/gi;
var matches_array = str.match(regexp);
console.log(matches_array);
// ['A', 'B', 'C', 'D', 'E', 'a', 'b', 'c', 'd', 'e']
String.prototype.matchAll()
- 返回一个包含所有匹配正则表达式及分组捕获结果的迭代器。
const regexp = RegExp('foo*','g');
const str = 'table football, foosball';
while ((matches = regexp.exec(str)) !== null) {
console.log(`Found ${matches[0]}. Next starts at ${regexp.lastIndex}.`);
// expected output: "Found foo. Next starts at 9."
// expected output: "Found foo. Next starts at 19."
}
//===
const regexp = RegExp('foo*','g');
const str = 'table football, foosball';
let matches = str.matchAll(regexp);
for (const match of matches) {
console.log(match);
}
// Array [ "foo" ]
// Array [ "foo" ]
// matches iterator is exhausted after the for..of iteration
// Call matchAll again to create a new iterator
matches = str.matchAll(regexp);
Array.from(matches, m => m[0]);
// Array [ "foo", "foo" ]
String.prototype.normalize()
- 按照指定的一种 Unicode 正规形式将当前字符串正规化.
// Initial string
// U+1E9B: LATIN SMALL LETTER LONG S WITH DOT ABOVE
// U+0323: COMBINING DOT BELOW
var str = "\u1E9B\u0323";
// Canonically-composed form (NFC)
// U+1E9B: LATIN SMALL LETTER LONG S WITH DOT ABOVE
// U+0323: COMBINING DOT BELOW
str.normalize("NFC"); // "\u1E9B\u0323"
str.normalize(); // same as above
// Canonically-decomposed form (NFD)
// U+017F: LATIN SMALL LETTER LONG S
// U+0323: COMBINING DOT BELOW
// U+0307: COMBINING DOT ABOVE
str.normalize("NFD"); // "\u017F\u0323\u0307"
// Compatibly-composed (NFKC)
// U+1E69: LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE
str.normalize("NFKC"); // "\u1E69"
// Compatibly-decomposed (NFKD)
// U+0073: LATIN SMALL LETTER S
// U+0323: COMBINING DOT BELOW
// U+0307: COMBINING DOT ABOVE
str.normalize("NFKD"); // "\u0073\u0323\u0307"
String.prototype.padEnd()
- 会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。
'abc'.padEnd(10); // "abc "
'abc'.padEnd(10, "foo"); // "abcfoofoof"
'abc'.padEnd(6, "123456"); // "abc123"
'abc'.padEnd(1); // "abc"
String.prototype.padStart()
- 用另一个字符串填充当前字符串(重复,如果需要的话),以便产生的字符串达到给定的长度。填充从当前字符串的开始(左侧)应用的。
'abc'.padStart(10); // " abc"
'abc'.padStart(10, "foo"); // "foofoofabc"
'abc'.padStart(6,"123465"); // "123abc"
'abc'.padStart(8, "0"); // "00000abc"
'abc'.padStart(1); // "abc"
String.prototype.repeat()
- 构造并返回一个新字符串,该字符串包含被连接在一起的指定数量的字符串的副本。
"abc".repeat(-1) // RangeError: repeat count must be positive and less than inifinity
"abc".repeat(0) // ""
"abc".repeat(1) // "abc"
"abc".repeat(2) // "abcabc"
"abc".repeat(3.5) // "abcabcabc" 参数count将会被自动转换成整数.
"abc".repeat(1/0) // RangeError: repeat count must be positive and less than inifinity
({toString : () => "abc", repeat : String.prototype.repeat}).repeat(2)
//"abcabc",repeat是一个通用方法,也就是它的调用者可以不是一个字符串对象.
String.prototype.replace()
- 返回一个由替换值(replacement)替换一些或所有匹配的模式(pattern)后的新字符串。模式可以是一个字符串或者一个正则表达式,替换值可以是一个字符串或者一个每次匹配都要调用的回调函数。
var p = 'The quick brown fox jumps over the lazy dog. If the dog reacted, was it really lazy?';
var regex = /dog/gi;
console.log(p.replace(regex, 'ferret'));
// expected output: "The quick brown fox jumps over the lazy ferret. If the ferret reacted, was it really lazy?"
console.log(p.replace('dog', 'monkey'));
// expected output: "The quick brown fox jumps over the lazy monkey. If the dog reacted, was it really lazy?"
String.prototype.search()
- 执行正则表达式和 String 对象之间的一个搜索匹配。
var paragraph = 'The quick brown fox jumps over the lazy dog. If the dog barked, was it really lazy?';
// any character that is not a word character or whitespace
var regex = /[^\w\s]/g;
console.log(paragraph.search(regex));
// expected output: 43
console.log(paragraph[paragraph.search(regex)]);
// expected output: "."
String.prototype.slice()
- 提取某个字符串的一部分,并返回一个新的字符串,且不会改动原字符串。
var str = 'The quick brown fox jumps over the lazy dog.';
console.log(str.slice(31));
// expected output: "the lazy dog."
console.log(str.slice(4, 19));
// expected output: "quick brown fox"
console.log(str.slice(-4));
// expected output: "dog."
console.log(str.slice(-9, -5));
// expected output: "lazy"
String.prototype.split()
- 使用指定的分隔符字符串将一个String对象分割成字符串数组,以将字符串分隔为子字符串,以确定每个拆分的位置。
var str = 'The quick brown fox jumps over the lazy dog.';
var words = str.split(' ');
console.log(words[3]);
// expected output: "fox"
var chars = str.split('');
console.log(chars[8]);
// expected output: "k"
var strCopy = str.split();
console.log(strCopy);
// expected output: Array ["The quick brown fox jumps over the lazy dog."]
String.prototype.substring()
- 返回一个字符串在开始索引到结束索引之间的一个子集, 或从开始索引直到字符串的末尾的一个子集。
var anyString = "Mozilla";
// 输出 "Moz"
console.log(anyString.substring(0,3));
console.log(anyString.substring(3,0));
console.log(anyString.substring(3,-3));
console.log(anyString.substring(3,NaN));
console.log(anyString.substring(-2,3));
console.log(anyString.substring(NaN,3));
// 输出 "lla"
console.log(anyString.substring(4,7));
console.log(anyString.substring(7,4));
// 输出 ""
console.log(anyString.substring(4,4));
// 输出 "Mozill"
console.log(anyString.substring(0,6));
// 输出 "Mozilla"
console.log(anyString.substring(0,7));
console.log(anyString.substring(0,10));
String.prototype.toLocaleLowerCase()
- 根据任何特定于语言环境的案例映射,返回调用字符串值转换为小写的值
console.log('ALPHABET'.toLocaleLowerCase());
// 'alphabet'
console.log('中文简体 zh-CN || zh-Hans'.toLocaleLowerCase());
// '中文简体 zh-cn || zh-hans'
String.prototype.toLocaleUpperCase()
- 本地化(locale-specific)的大小写映射规则将输入的字符串转化成大写形式并返回结果字符串。
console.log('alphabet'.toLocaleUpperCase()); // 'ALPHABET'
String.prototype.toLowerCase()
- 将调用该方法的字符串值转为小写形式,并返回。
console.log('中文简体 zh-CN || zh-Hans'.toLowerCase());
// 中文简体 zh-cn || zh-hans
console.log( "ALPHABET".toLowerCase() );
// "alphabet"
String.prototype.toUpperCase()
- 调用该方法的字符串值转换为大写形式,并返回。
console.log( "alphabet".toUpperCase() ); // "ALPHABET"
String.prototype.toString()
- 返回指定对象的字符串形式。
var x = new String("Hello world");
alert(x.toString()) // 输出 "Hello world"
String.prototype.trim()
- 会从一个字符串的两端删除空白字符。在这个上下文中的空白字符是所有的空白字符 (space, tab, no-break space 等) 以及所有行终止符字符(如 LF,CR)。
var orig = ' foo ';
console.log(orig.trim()); // 'foo'
// 另一个.trim()例子,只从一边删除
var orig = 'foo ';
console.log(orig.trim()); // 'foo'
String.prototype.trimRight()/trimEnd()
- 从一个字符串的右端移除空白字符。trimRight()trimEnd()方法的别名。
var greeting = ' Hello world! ';
console.log(greeting);
// expected output: " Hello world! ";
console.log(greeting.trimEnd());
console.log(greeting.trimRight());
// expected output: " Hello world!";
String.prototype.trimLeft()/trimStart()
- 从字符串的开头删除空格。trimLeft()是trimStart()方法的别名。
var str = " foo ";
alert(str.length); // 8
str = str.trimLeft();
alert(str.length); // 5
document.write( str );
String.prototype.valueOf()
- 返回一个String对象的原始值(primitive value)。
x = new String("Hello world");
alert(x.valueOf()) // Displays "Hello world"
String.raw()
- 是一个模板字符串的标签函数,它的作用类似于 Python 中的字符串前缀 r 和 C# 中的字符串前缀 @(还是有点区别的,详见隔壁 Chromium 那边的这个 issue),是用来获取一个模板字符串的原始字符串的,比如说,占位符(例如 ${foo})会被处理为它所代表的其他字符串,而转义字符(例如 \n)不会。
String.raw`Hi\n${2+3}!`;
// 'Hi\n5!',Hi 后面的字符不是换行符,\ 和 n 是两个不同的字符
String.raw `Hi\u000A!`;
// "Hi\\u000A!",同上,这里得到的会是 \、u、0、0、0、A 6个字符,
// 任何类型的转义形式都会失效,保留原样输出,不信你试试.length
let name = "Bob";
String.raw `Hi\n${name}!`;
// "Hi\nBob!",内插表达式还可以正常运行
// 正常情况下,你也许不需要将 String.raw() 当作函数调用。
// 但是为了模拟 `t${0}e${1}s${2}t` 你可以这样做:
String.raw({ raw: 'test' }, 0, 1, 2); // 't0e1s2t'
// 注意这个测试, 传入一个 string, 和一个类似数组的对象
// 下面这个函数和 `foo${2 + 3}bar${'Java' + 'Script'}baz` 是相等的.
String.raw({
raw: ['foo', 'bar', 'baz']
}, 2 + 3, 'Java' + 'Script'); // 'foo5barJavaScriptbaz'
ES6
ES6的新特性
let关键字,用于声明只在块级作用域起作用的变量const关键字,用于声明常量- 结构赋值,一种新的变量赋值方式
Symbol数据类型,定义一个独一无二的值for...of遍历器,可遍历具有iterator(迭代器)接口的数据类型Set结构,用于存储不重复成员值的集合Map结构Promise对象,一种异步操作的解决方案,更合理、规范的操作异步处理。class类,定义类和更简便的实现类的继承- 模板字符串
- 函数参数默认值
- fetch
var、let、const的区别
var在任何语句执行前就完成了声明和初始化funciton声明、初始化和赋值在一开始就全部完成,函数变量提升的优先级更高let先声明但并没有初始化,只有解析到let那一行才会进入初始化阶段。如果在此作用域前提前访问,则报错xx is not defined, 暂时性死区,在相同作用域内不允许重复声明同一个变量const和let区别在于const声明的是一个只读常亮,一旦声明后就不能改变
let const 优点
- 不会变量提升
- 变量不能重复声明
- 有块级作用域
es6 generator 是什么,async/await 实现原理
ES6和node的commonjs模块化规范区别
ES6 都有什么 Iterator 遍历器
1、遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)
2、Iterator 的作用有三个:
- 一是为各种数据结构,提供一个统一的、简便的访问接口;
- 二是使得数据结构的成员能够按某种次序排列;
- 三是 ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费。
3、默认部署了 Iterator 的数据有 Array、Map、Set、String、TypedArray、arguments、NodeList 对象,ES6 中有的是 Set、Map、
ES6 中类的定义
// 1、类的基本定义
class Parent {
constructor(name = "小白") {
this.name = name;
}
}
//=====================================
// 2、生成一个实例
let g_parent = new Parent();
console.log(g_parent); //{name: "小白"}
let v_parent = new Parent("v"); // 'v'就是构造函数name属性 , 覆盖构造函数的name属性值
console.log(v_parent); // {name: "v"}
//=====================================
// 3、继承
class Parent {
//定义一个类
constructor(name = "小白") {
this.name = name;
}
}
class Child extends Parent {}
console.log("继承", new Child()); // 继承 {name: "小白"}
//=====================================
// 4、继承传递参数
class Parent {
//定义一个类
constructor(name = "小白") {
this.name = name;
}
}
class Child extends Parent {
constructor(name = "child") {
// 子类重写name属性值
super(name); // 子类向父类修改 super一定放第一行
this.type = "preson";
}
}
console.log("继承", new Child("hello")); // 带参数覆盖默认值 继承{name: "hello", type: "preson"}
//=====================================
// 5、ES6重新定义的ES5中的访问器属性
class Parent {
//定义一个类
constructor(name = "小白") {
this.name = name;
}
get longName() {
// 属性
return "mk" + this.name;
}
set longName(value) {
this.name = value;
}
}
let v = new Parent();
console.log("getter", v.longName); // getter mk小白
v.longName = "hello";
console.log("setter", v.longName); // setter mkhello
//=====================================
// 6、类的静态方法
class Parent {
//定义一个类
constructor(name = "小白") {
this.name = name;
}
static tell() {
// 静态方法:通过类去调用,而不是实例
console.log("tell");
}
}
Parent.tell(); // tell
//=====================================
// 7、类的静态属性:
class Parent {
//定义一个类
constructor(name = "小白") {
this.name = name;
}
static tell() {
// 静态方法:通过类去调用,而不是实例
console.log("tell"); // tell
}
}
Parent.type = "test"; // 定义静态属性
console.log("静态属性", Parent.type); // 静态属性 test
let v_parent = new Parent();
console.log(v_parent); // {name: "小白"} 没有tell方法和type属性
谈谈你对 ES6 的理解
es6 是一个新的标准,它包含了许多新的语言特性和库,是 JS 最实质性的一次升级。
比如'箭头函数'、'字符串模板'、'generators(生成器)'、'async/await'、'解构赋值'、'class'等等,还有就是引入 module 模块的概念。
箭头函数可以让 this 指向固定化,这种特性很有利于封装回调函数
- (1)函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
- (2)不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
- (3)不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 Rest 参数代替。
- (4)不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
- async/await 是写异步代码的新方式,以前的方法有回调函数和 Promise。
- async/await 是基于 Promise 实现的,它不能用于普通的回调函数。async/await 与 Promise 一样,是非阻塞的。
- async/await 使得异步代码看起来像同步代码,这正是它的魔力所在。
promise的原理和实现
- promise是同步执行的,promise.then是异步的
- promise有三种状态。pending、 fulfilled、rejected,状态一旦改变就不会在变
- resolve/reject多次调用,只有第一次有效
- promise每次调用都会返回一个新的promise,从而实现链式调用
解构赋值及其原理
- ES6允许按照一定的规则,从数组或对象提取值,按照对应的位置对变量进行赋值
箭头函数和普通函数的区别
- 箭头函数的语法定义要比普通函数简洁,清晰。
- 函数体内的
this对象是定义是所在的对象,而不是使用时所在的对象 - 不可以当构造函数,也就是说不能用
new命令 - 函数体内不存在arguments对象。可以使用参数代替
- 不可以使用yield命令。
es6的继承和es5的继承有什么区别
- ES5的继承是通过原型或者构造函数实现的。
- ES6封装了class 用Extends来继承
promise封装ajax
function postJson(url, data) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', url);
xhr.onreadystatuschange = () => {
if(xhr.readyState !== 4) return;
if(xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject('请求失败')
}
}
xhr.send(JSON.stringify(data));
})
}
Vue全家桶
Vue
vue中的key的作用
- 带key
key给每个vnode(虚拟节点)增加一个唯一ID,在更新组件是判断两个节点是否相同。相同就复用,不相同就删除旧创建新的。
虽然带上key会增加开销,但是对于用户来说基本感受不到差距,而且能保证组件状态正确。
- 不带key
不带key时节点能够复用,省去了销毁/创建组件的开销,同时只需要修改DOM文本内容而不是移除/添加节点。
这种模式diff速度可能会更快,但也会带来一些隐藏的副作用,比如可能不会产生过渡效果,或者在某些节点有绑定数据(表单)状态,会出现状态错位。 这种模式只适用于渲染简单的无状态组件
vue双向数据绑定原理
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
具体步骤:
-
第一步:需要
observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化 -
第二步:
compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图 -
第三步:
Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:- 1、在自身实例化时往属性订阅器(dep)里面添加自己
- 2、自身必须有一个
update()方法 - 3、待属性变动dep.notice()通知时,能调用自身的 update() 方法,并触发Compile中绑定的回调,则功成身退。
-
第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
Vue的生命周期
Vue生命周期一共分为8个阶段
- 创建前/后
- 在
beforeCreated阶段,实例的$el、data和methods都未初始化。this指向创建的实例 - 在
created阶段,实例的data和methods都初始化完成,但DOM节点还未挂载,不能访问到$el属性,$ref属性为空数组
- 在
- 载入前/后
- 在
beforeMount阶段,实例找到相对应的template并编译成render函数 - 在
mouted阶段,实例将el挂载到DOM上,可以获取到DOM节点,$ref属性可以访问
- 在
- 更新前/后
- 当
data变化时,会触发beforeUpdate和updated
- 当
- 销毁前/后
- 执行
destroy方法后,data的改变不会再触发周期函数,此时Vue实例已经解除了事件监听和DOM绑定,但DOM结构依然存在。
- 执行

Vue组件间的参数传递
- 父传子,父组件通过自定义绑定值,子组件通过props接收数据。
- 子传父,父组件通过
on自定义事件绑定方法来接收值,子组件通过$emit触发自定义事件传递参数给父组件 - 非父子组件间的数据传递,兄弟组件传值,用EventBus创建一个事件中心,用来传递/接收事件;
- Vuex 状态管理
keep-alive的作用是什么
- 包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染。
Vue性能优化
- 打包优化
-
小图片转base64
-
静态下图片使用雪碧图
-
生成环境屏蔽sourceMap
-
开启gzip压缩文件,减少文件大小
-
第三方依赖按需加载
-
vue-router懒加载/异步路由
-
-
代码优化
- 模板里不写过多表达式
- 遍历渲染列表数据时,为每一项设置唯一的key值
- 切换频繁使用v-show,不频繁使用v-if
- 在适当的环境中使用keep-alive组件,用来缓存组件,避免多次加载相应的组件,减少性能消耗。(tab切换)
- 利用Object.freeze()提升性能
- 用户体验优化
- 移动端项目,click事件有300ms延迟,应使用touch事件
- 资源加载完成前可以使用loading动画
- 首屏加载资源较多,出现闪屏/白屏的情况,可以使用
骨架屏
Vuex
什么是Vuex
- Vuex是一个专为Vue服务,用于管理页面数据状态、提供统一数据操作的生态系统。
Vuex 原理
- vuex 仅仅是作为 vue 的一个插件而存在,不像 Redux,MobX 等库可以应用于所有框架,vuex 只能使用在 vue 上,很大的程度是因为其高度依赖于 vue 的 computed 依赖检测系统以及其插件系统,
- vuex 整体思想诞生于 flux,可其的实现方式完完全全的使用了 vue 自身的响应式设计,依赖监听、依赖收集都属于 vue 对对象 Property set get 方法的代理劫持。最后一句话结束 vuex 工作原理,vuex 中的 store 本质就是没有 template 的隐藏着的 vue 组件;
Vuex的特性
- 每一个Vuex应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。
- Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变store中的状态。改变store中的状态的唯一途径就是显式地提交(commit)mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
Vuex有哪些属性
- Vuex有五种属性,分别是
State,Getter,Mutation,Action,Module
Vuex的state特性
state为单一状态数,是数据源存放的地方(对应Vue对象里的data),存放的数据是响应式的,Vue组件从store读取数据,当store中的数据发生变化则依赖数据的组件也会发生变化。- 当一个组件需要获取多个状态时,可以使用
mapState辅助函数把state映射到组件的computed计算属性上
Vuex的getter特性
- 当需要从store的state中派生出一些状态时,就需要使用getter.
- getter的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
Vuex的mutation特性
- 更改Store中状态的唯一方式就是提交mutation。
- mutation非常类似于事件
- 每个mutation都有一个事件类型(type)和回调函数(handler)
- 要执行这个回调函数需要调用
store.commit
Vuex的action特性
- action类似于mutation,不同在于action提交的是mutation,而不能直接改变状态。
- action可以包含任何异步操作
- 如果需要调用action,则需通过
store.dispatch触发
Vuex的module特性
- 使用单一状态树,导致应用的所有状态集中到一个很大的对象,当应用变得很大时,store 对象会变得臃肿不堪。
- 通过分割模块,每个模块拥有自己的 state、mutation、action、getters、甚至是嵌套子模块
ajax请求
- 如果请求回来的数据如果被其他组件复用则将请求放入action中,包装成promise返回。
- 如果不需要被其他组件复用则可以放在组件内部。
不用Vuex会带来什么问题
- 可维护性会下降,你要想修改数据,你得维护三个地方(父组件属性->子组件props->子组件))
- 可读性会下降,因为一个组件里的数据,你根本就看不出来是从哪来的
- 增加耦合,大量的上传派发,会让耦合性大大的增加,本来Vue用Component就是为了减少耦合,现在这么用,和组件化的初衷相背。
- 兄弟组件有大量通信的,建议一定要用,不管大项目和小项目,因为这样会省很多事
VueRouter
懒加载/按需加载路由
-
异步组件
- 组件生成一个js文件
component: resolve => require(['../components/PromiseDemo'], resolve)
-
import
const ImportFuncDemo2 = () => import('../components/ImportFuncDemo2')component: ImportFuncDemo2
-
webpack
require.ensure()- 多个路由指定相同的chunkName,会合并打包成一个js文件
component: resolve => require.ensure([], () => resolve(require('../components/PromiseDemo')), 'demo')
-
参考:vue项目按需加载
路由之间跳转
- 使用
router-link创建a标签来定义导航链接 - 编程式的导航 router.push
router.push({ path: 'home' })
导航守卫
-
全局守卫
- 全局前置守卫
router.beforeEach - 全局解析守卫
router.beforeResolve(2.5.0 新增) - 全局后置守卫
router.afterEach
- 全局前置守卫
-
路由独享守卫
beforeEnter
-
组件守卫
beforeRouteEnterbeforeRouteUpdate(2.2 新增)beforeRouteLeave
怎么定义 vue-router 的动态路由? 怎么获取传过来的值
- 在 router 目录下的 index.js 文件中,对 path 属性加上 /:id。
- 使用 router 对象的 params.id 获取
vue-router模式
- hash
- history
VueCli
重要 未写
Vue源码
项目中遇到的问题
微信小程序
1. 简单描述下微信小程序的相关文件类型
微信小程序项目结构主要有四个文件类型
WXML:WeiXin Markup Language是框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构。内部主要是微信自己定义的一套组件WXSS:WeiXin Style Sheets是一套样式语言,用于描述WXML的组件样式js: 逻辑处理,网络请求json: 小程序设置,如页面注册,页面标题及tabBar
主要文件
app.json必须要有这个文件,如果没有这个文件,项目无法运行,因为微信框架把这个作为配置文件入口,整个小程序的全局配置。包括页面注册,网络设置,以及小程序的window背景色,配置导航条样式,配置默认标题app.js必须要有这个文件,没有也是会报错!但是这个文件创建一下就行 什么都不需要写以后我们可以在这个文件中监听并处理小程序的生命周期函数、声明全局变量app.wxss可选
2. 简述微信小程序原理
微信小程序采用
JavaScript、WXML、WXSS三种技术进行开发,本质就是一个单页面应用,所有的页面渲染和事件处理,都在一个页面内进行,但又可以通过微信客户端调用原生的各种接口
微信的架构,是数据驱动的架构模式,它的
UI和数据是分离的,所有的页面更新,都需要通过对数据的更改来实现
小程序分为两个部分
webview和appService。其中webview主要用来展现UI,appService有来处理业务逻辑、数据及接口调用。它们在两个进程中运行,通过系统层JSBridge实现通信,实现UI的渲染、事件的处理
3. 小程序的双向绑定和vue哪里不一样
小程序直接 this.data 的属性是不可以同步到视图的,必须调用:
this.setData({
// 这里设置
})
4. 小程序的wxss和css有哪些不一样的地方
WXSS和CSS类似,不过在CSS的基础上做了一些补充和修改
- 尺寸单位
rpx
rpx 是响应式像素,可以根据屏幕宽度进行自适应。规定屏幕宽为 750rpx。如在 iPhone6 上,屏幕宽度为 375px,共有 750 个物理像素,则 750rpx = 375px = 750 物理像素
- 使用
@import标识符来导入外联样式。@import后跟需要导入的外联样式表的相对路径,用;表示语句结束
/** index.wxss **/
@import './base.wxss';
.container{
color: red;
}
5. 小程序页面间有哪些传递数据的方法
- 使用全局变量实现数据传递
在
app.js文件中定义全局变量globalData, 将需要存储的信息存放在里面
// app.js
App({
// 全局变量
globalData: {
userInfo: null
}
})
使用的时候,直接使用 getApp() 拿到存储的信息
- 使用
wx.navigateTo与wx.redirectTo的时候,可以将部分数据放在url里面,并在新页面onLoad的时候初始化
//pageA.js
// Navigate
wx.navigateTo({
url: '../pageD/pageD?name=raymond&gender=male',
})
// Redirect
wx.redirectTo({
url: '../pageD/pageD?name=raymond&gender=male',
})
// pageB.js
...
Page({
onLoad: function(option){
console.log(option.name + 'is' + option.gender)
this.setData({
option: option
})
}
})
需要注意的问题:
wx.navigateTo 和 wx.redirectTo 不允许跳转到 tab 所包含的页面
onLoad 只执行一次
- 使用本地缓存
Storage相关
6. 小程序的生命周期函数
onLoad页面加载时触发。一个页面只会调用一次,可以在onLoad的参数中获取打开当前页面路径中的参数onShow()页面显示/切入前台时触发onReady()页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互onHide()页面隐藏/切入后台时触发。 如navigateTo或底部tab切换到其他页面,小程序切入后台等onUnload()页面卸载时触发。如redirectTo或navigateBack到其他页面时
详见 生命周期回调函数
. 怎么封装微信小程序的数据请求
wx.request({
url: 'test.php', //仅为示例,并非真实的接口地址
data: {
x: '' ,
y: ''
},
success: function(res) {
console.log(res.data)
}
})
// promise
参考 这里
8. 哪些方法可以用来提高微信小程序的应用速度
1、提高页面加载速度
2、用户行为预测
3、减少默认 data 的大小
4、组件化方案
9. 微信小程序的优劣势
优势
- 即用即走,不用安装,省流量,省安装时间,不占用桌面
- 依托微信流量,天生推广传播优势
- 开发成本比
App低
缺点
- 用户留存,即用即走是优势,也存在一些问题
- 入口相对传统
App要深很多 - 限制较多,页面大小不能超过2M。不能打开超过10个层级的页面
10. 怎么解决小程序的异步请求问题
小程序支持大部分
ES6语法
- 在返回成功的回调里面处理逻辑
Promise异步
11. 小程序关联微信公众号如何确定用户的唯一性
如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),可通过
unionid来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的unionid是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid是相同的
12. 如何实现下拉刷新
- 首先在全局
config中的window配置enablePullDownRefresh - 在
Page中定义onPullDownRefresh钩子函数,到达下拉刷新条件后,该钩子函数执行,发起请求方法 - 请求返回后,调用
wx.stopPullDownRefresh停止下拉刷新
参考 这里
13. bindtap和catchtap的区别是什么
相同点:首先他们都是作为点击事件函数,就是点击时触发。在这个作用上他们是一样的,可以不做区分
不同点:他们的不同点主要是 bindtap 是不会阻止冒泡事件的,catchtap 是阻值冒泡的
14. 简述下 wx.navigateTo(), wx.redirectTo(), wx.switchTab(), wx.navigateBack(), wx.reLaunch()的区别
wx.navigateTo():保留当前页面,跳转到应用内的某个页面。但是不能跳到tabbar页面wx.redirectTo():关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到tabbar页面wx.switchTab():跳转到abBar页面,并关闭其他所有非tabBar页面wx.navigateBack()关闭当前页面,返回上一页面或多级页面。可通过getCurrentPages()获取当前的页面栈,决定需要返回几层wx.reLaunch():关闭所有页面,打开到应用内的某个页面
React全家桶
- 基本语法
- 生命周期
框架
- bootstrap
- element
- Echarts
- Swiper
Node\Express\Egg\Koa
- 熟悉几道常用面试题
开发中遇到的问题
为什么 0.1 + 0.2 != 0.3 ?
JS 采用的是双精度版本
- 内部的原理是什么?
我们计算机的信息全部转化为二进制进行存储的,那么0.1的二进制表示的是一个无限循环小数, 该版本的 JS 采用的是浮点数标准需要对这种无限循环的二进制进行截取,从而导致了精度丢失, 造成了0.1不再是0.1,截取之后0.1变成了 0.100...001,0.2变成了0.200...002。所以两者相加的数大于0.3。
- 控制台上输出console.log(0.1)还等于0.1?
因为在输入内容进行转换的时候,二进制转换成十进制,然后十进制转换成字符串, 在这个转换的过程中发生了取近似值,所以打印出来的是一个近似值。
部分手机第三方输入法会将页面网上挤的问题
// 特定需求页面,比如评论页面,输入框在顶部之类的
const interval = setInterval(function() {
document.body.scrollTop = 0;
}, 100)
// 注意关闭页面或者销毁组件的时候记得清空定时器
clearInterval(interval);
- 设置一个100ms的定时器,把scrollTop设置成0,当点击完成是清空定时器
ios竖屏拍照上传,图片被旋转问题
flex对低版本的ios和Android的支持问题
/**使用postcss的autoprefixer插件,自动添加浏览器内核前缀,
并且增加更低浏览器版本的配置,自动添加flex老版本的属性和写法**/
autoprefixer({
browsers: [
'iOS >= 6', // 特殊处理支持低版本IOS
'Safari >= 6', // 特殊处理支持低版本Safari
],
}),
ios上自动识别电话
<meta content="telephone=no" name="format-detection" />
android上自动识别邮箱
<meta content="email=no" name="format-detection" />
移动端click300ms延迟
原因:click事件要等待是否是双击事件,会有300ms的延迟
解决:使用touchstart/touchend或者使用zepto的tap事件替代click
图片优化
- 小图片过多,导致请求增多
- 使用精灵图合并成大图,缺点: 如果换图的化非常麻烦
- 把图标转成base64
- 首屏加载慢,到时用户体验极差
- 图片懒加载,
- 静态资源放到CDN