这个是小浪学习前端的时候对于一些面试的基础知识的总结(这里面没涉及框架),不是很全,一方面是和大家一起分享,有错误的地方请大家多多指教我这个新人,另一方面方便自己复习,自己总结做笔记也是加强印象的一种方式,加油喔!小浪努力学前端
HTML基础
1.如何理解HTML语义化
为什么要语义化
-
页面不止是给人看的,机器也要看爬虫也要看
-
如果全部都是div+css布局,有的时候页面因为一些原因加载不出来样式,全部都是div页面对用户不友好
特点
- 让人更加容易读懂,有利于构建清晰的结构(增加代码的可读性)
- 让搜索引擎更加容易读懂(方便SEO)
2.语义化的标签有哪些
html5之前的常用的语义化标签
h1~h6 p br ul ol li dl dt dd em strong table thead tobdy tfoot td th caption
注意的点:
b
、font
、u
等纯样式标签不要使用strong
是为了强调重要而加粗(不要用b
b
是为了加粗而加粗),em
是斜体是强调(不用i
i
就是斜体)- 每个
input
标签对应的说明文本都需要使用label
标签 - 表单域要使用
fieldset
包起来,并使用legend
说明表单的用途
html5新增的常用的语义化标签
header footer nav aside section artice
3.块级元素 和 内联元素
块级元素 :
ul li ol dl dd dt table h1-h6 form p
等等 display : block
内联元素 :
a span b img input button
等等 display : inline-block
样式转换:
display:block
行内元素转换为块级元素display:inline
块级元素转换为行内元素display:inline-block
转为内联元素
4.DOM怎么优化
-
可以使用伪元素,阴影实现的内容尽量不使用
-
DOM
实现,如清除浮动、样式实现等;按需加载,减少不必要的渲染; -
结构合理,语义化标签
-
使用文档片段
-
DOM
缓存 -
innerHTML
代替appendChild
-
虚拟
DOM
CSS基础
1.盒子模型的宽度计算
标准盒子模型:
默认
box-sizing : content-box
offsetWidth : 包括 width + border + padding (不包括margin)
比如这个例子
#box {
width : 20px;
padding : 20px;
margin : 20px;
border : 2px solid #ccc;
}
这里计算就是 offsetWidth :
document.getElementById('box').offsetWidth
64
弹性盒子的计算
加上
box-sizing : border-box
总的宽度设置多少就是多少(width 设置多少就是多少)
然后内容的宽度是自己计算的
#box {
width : 80px;
padding : 20px;
margin : 20px;
border : 2px solid #ccc;
box-sizing : border-box;
}
这里计算就是 offsetWidth :
document.getElementById('box').offsetWidth
80
如果padding + border 的宽度大于 width
这个时候的offsetWidth 就是 padding + border了
2.marign纵向重叠的问题
margin重叠是指两个或多个盒子(可能相邻也可能嵌套)的相邻边界(其间没有任何非空内容、补白、边框)重合在一起而形成一个单一边界
比如就有以下的问题:
计算第一行和最后一行之间的距离
<style>
p {
font-size: 16px;
margin-top: 20px;
margin-bottom: 10px;
line-height: 1;
}
</style>
</head>
<body>
<p>第一行</p>
<p></p>
<p></p>
<p></p>
<p></p>
<p></p>
<p>末行</p>
</body>
中间的空着的p都没了 ,因为没有高度
最后一行和 第一行之间的距离是
20px
因为在
margin
纵向会有重叠
计算方法:
-
全部都为正值,取最大者;
-
不全是正值,则都取绝对值,然后用正值的最大值减去绝对值的最大值;
3.margin负值
margin-top
margin-left
负值,元素向上、向左移动margin-right
负值 ,右侧元素左移(“后续元素”会被拉向指定方向),元素自身不变margin-bottom
负值,右侧元素上移动(“后续元素”会被拉向指定方向), 元素自身不变
4.BFC
全称:
Block Formatting Context
, 名为 "块级格式化上下文"。
BFC
是一个完全独立的空间(一块独立的渲染区域),让空间里的子元素不会影响到外面的布局
BFC规则
BFC
就是一个块级元素,块级元素会在垂直方向一个接一个的排列BFC
就是页面中的一个隔离的独立容器,容器里的标签不会影响到外部标签- 垂直方向的距离由margin决定, 属于同一个
BFC
的两个相邻的标签外边距会发生重叠 - 计算
BFC
的高度时,浮动元素也参与计算
形成BFC常见条件
float
不为none
position
为absolute
fixed
overflow
不是visible
display
为flex
inline-block
table-cell
等
常用的情景
清除浮动
margin重叠
5.float布局
双飞翼布局 和 圣杯布局是PC端的经典布局了
两侧的宽度写死,中间的宽度自适应
公共结构如下
<body>
<div class="container">
<div class="main"></div>
<div class="left"></div>
<div class="right"></div>
</div>
</body>
公共样式
给
main
设置width: 100%
,让它始终占满窗口,这样才有自适应的效果。
<style type="text/css">
body {
margin: 0;
width: 700px;
min-width: 550px;
}
.container {
width: 100%;
height: 30vh;
}
.container > div {
height: 100%;
float: left;
}
.main {
width: 100%;
background-color: aqua;
}
.left {
width: 100px;
background-color: red;
}
.right {
width: 200px;
background-color: green;
}
</style>
圣杯布局
为三个元素的父元素加上
padding
属性,腾开位置
.container {
width: 100%;
height: 30vh;
padding-left: 100px;
padding-right: 200px;
}
left要放到main的左边,设置
margin-left: -100%
,因为margin
的百分比是相对与父元素的,所以需要整整一行的宽度才能补偿这个margin的值,所以left就能到main的位置。然后再通过相对定位 right自身的宽度就到了最左边
.left {
position: relative;
margin-left: -100%;
right: 100px;
width: 100px;
background-color: red;
}
right到main的右边,比如
margin-right: -200px;
,正好使main重叠right的宽度,因为设置了浮动所以right就会到main的右边了。
.right {
margin-right: -200px;
width: 200px;
background-color: green;
}
双飞翼布局
双飞翼布局需要更改下布局,因为main设置了100% ,不能直接给main设置margin
样式
<style type="text/css">
body {
margin: 0;
width: 550px;
min-width: 500px;
}
.container {
width: 100%;
height: 30vh;
}
.container > div {
height: 100%;
float: left;
}
.wrap {
background-color: aqua;
height: 100%;
margin: 0 200px 0 100px;
}
.main {
width: 100%;
}
.left {
width: 100px;
background-color: red;
}
.right {
width: 200px;
background-color: green;
}
</style>
结构
<body>
<div class="container">
<div class="main">
<div class="wrap"></div>
</div>
<div class="left"></div>
<div class="right"></div>
</div>
</body>
再创造一个内容层,将所有要显示的内容放到
wrap
中,给wrap
设置margin
就可以了
left要放到main的左边,设置
margin-left: -100%
,因为margin
的百分比是相对与父元素的,所以需要整整一行的宽度才能补偿这个margin的值,所以left就能到main的左边。接着让right到main的右边,只需要设置
margin-left
的值为负的right的宽,比如margin-left: -200px;
,正好使main重叠right的宽度,因为设置了浮动所以right就会到main的右边了。
.left {
margin-left: -100%;
width: 100px;
background-color: red;
}
.right {
margin-left: -200px;
width: 200px;
background-color: green;
}
使用 flex也可以达到效果
css样式
html,
body {
margin: 0;
padding: 0;
}
.box {
display: flex;
/* 左中右排列 */
justify-content: space-between;
height: 100px;
}
.center {
/* 自己填充剩下的区域 */
flex: 1;
height: 100px;
background-color: red;
}
.left,
.right {
/* 缩小放大的比例 所占空间的大小 */
flex: 0 0 200px;
height: 100px;
background-color: skyblue;
}
html结构
<body>
<div class="box">
<div class="left"></div>
<div class="center"></div>
<div class="right"></div>
</div>
</body>
使用定位来实现
最常使用的是定位来实现
css样式
html,
body {
margin: 0;
padding: 0;
}
.box {
height: 100px;
}
.center {
margin: 0 200px;
height: 100px;
background-color: red;
}
.left,
.right {
position: absolute;
top: 0;
width: 200px;
height: 100px;
background-color: skyblue;
}
.left {
left: 0;
}
.right {
right: 0;
}
结构和上面一样
<body>
<div class="box">
<div class="left"></div>
<div class="center"></div>
<div class="right"></div>
</div>
</body>
6.手写clearfix
一般的话有的时候会要求10s左右 手写clearfix ,写的时候最好熟练不要犹豫
.clearfix:after {
content : '';
display : table;
clear : both;
}
/*兼容IE低版本*/
.clearfix {
*zoom : 1;
}
7.flex实现一个三点的色子
flex基础的语法
介绍几个常用的
flex-direction
属性决定主轴的方向(即项目的排列方向)。
row
表示从左向右排列row-reverse
表示从右向左排列column
表示从上向下排列column-reverse
表示从下向上排列
flex-wrap
可以让Flex项目换行排列。
nowrap
(缺省):所有Flex项目单行排列wrap
:所有Flex项目多行排列,按从上到下的顺序wrap-reverse
:所有Flex项目多行排列,按从下到上的顺序
flex-flow
属性是flex-direction
属性和flex-wrap
属性的简写形式,默认值为row nowrap
justify-content
属性定义了项目在主轴上的对齐方式及额外空间的分配方式。
flex-start
(缺省):从启点线开始顺序排列flex-end
:相对终点线顺序排列center
:居中排列space-between
:项目均匀分布,第一项在启点线,最后一项在终点线space-around
:项目均匀分布,每一个项目两侧有相同的留白空间,相邻项目之间的距离是两个项目之间留白的和space-evenly
:项目均匀分布,所有项目之间及项目与边框之间距离相等
align-items
属性定义项目在交叉轴上的对齐方式。
stretch
(缺省):交叉轴方向拉伸显示flex-start
:项目按交叉轴起点线对齐flex-end
:项目按交叉轴终点线对齐center
:交叉轴方向项目中间对齐baseline
:交叉轴方向按第一行文字基线对齐
align-content
属性定义了在交叉轴方向的对齐方式及额外空间分配,类似于主轴上justify-content
的作用。
stretch
(缺省):拉伸显示flex-start
:从启点线开始顺序排列flex-end
:相对终点线顺序排列center
:居中排列space-between
:项目均匀分布,第一项在启点线,最后一项在终点线space-around
:项目均匀分布,每一个项目两侧有相同的留白空间,相邻项目之间的距离是两个项目之间留白
来实现一个三点的色了(简单)
实现的思路就是使用flex布局。主轴是
space-between
。第2个点交叉轴方向是居中对齐。 第3个点项目按交叉轴终点线对齐。
结构
<div class="box">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
样式
<style type="text/css">
.box {
width: 200px;
height: 200px;
border: 2px solid #ccc;
border-radius: 10px;
padding: 20px;
display: flex;
justify-content: space-between;
}
.item {
display: block;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: rgb(210, 225, 228);
}
.item:nth-child(2) {
align-self: center;
}
.item:nth-child(3) {
align-self: flex-end;
}
</style>
8.定位
absolute relative fixed 相对什么定位
static
默认的position值,无特殊定位,遵循标准文档流relative
相对于自身定位 ,但是还是占据原来的空间,同时可通过z-index
定义层叠关系。absolute
相对于该元素最近的已定位的祖先元素,如果没有一个祖先元素设置定位,那么参照物是body
层。不占据原来的空间,同时可通过z-index
定义层叠关系。fixed
相对于浏览器窗口进行固定定位,同时可通过z-index
定义层叠关系。如果他的祖先元素有transform
这个属性,它会参考他做固定定位
9.居中对齐的实现方式
水平居中
inline
元素 :text-align : center
block
元素 :margin : auto
absolute
元素 :left : 50%
+margin-left
负自身的一半
垂直居中
inline
元素 :line-heigth
等于height
absolute
元素 :top : 50%
+margin-top
负自身的一半 或transform (-50%,-50%)
absolute
元素 :top, buttom, left, right = 0
+margin : auto
flex 父盒子设置
display: flex;
justify-content: center;
align-items: center;
table-cell 这个是针对 不是盒子的元素居中,但是可以把里面的盒子
display:inline-block
.father{
display: table-cell;
vertical-align: middle;
text-align: center;
/*要求是固定的宽高不能是百分比*/
}
.son{
display: inline-block
}
10.line-height继承
- 写具体数值,如30px,则继承该值(比较好理解)
- 写比例,如2/ 1.5 ,则继承该比例(比较好理解)
- 写百分比, 如200% ,则继承计算出来的值(考点)
比如这么一道题 求p的行高
<style type="text/css">
body {
font-size: 16px;
line-height: 200%;
}
p {
font-size: 18;
}
</style>
结构
<body>
<p>A</p>
</body>
如果是百分比的话,他会先乘起来再继承 16*200%
如果是数字。直接继承,用自己的fontsize*数字
11.rem 是什么
rem是一个长度单位
- px,绝对长度单位,最常用
- em ,相对长度单位,相对于父元素,不常用
- rem ,相对长度单位,相对于根元素,常用于响应式布局
12.响应式布局的常用方案
- media-query ,根据不同的屏幕宽度设置根元素font-size
- rem,基于根元素的相对单位
13.rem的弊端:“阶梯”性
像下面的媒体查询 ,就比如中间的375~413 ,当屏幕的宽度达到376,378
font-size
还是100px,不会实时变化除非超过了这个范围
@media only screen and (max-width: 374px) {
/* iphone5 或者更小的尺寸,以iphone5 的宽度(320px)比例设置font-size */
html {
font-size: 86px;
}
}
@media only screen and (min-width: 375px) and (max-width: 413px) {
/* iphone6/7/8和iphone X */
html {
font-size: 100px;
}
}
@media only screen and (min-width: 414px) {
/* iphone6p 或者更大的尺寸,以iphone6p 的宽度(414px) 比例设置font-size */
html {
font-size: 110px;
}
}
14.网页视口尺寸
网页视口尺寸
window.screen.height
// 屏幕高度window.innerHeight
// 网页视口高度document.body.clientHeight
// body高度
vw/vh
vh
网页视口高度的1/100vw
网页视口宽度的1/100vmax
取两者最大值;vmin
取两者最小值
15.重绘和回流
重绘:指的是当页面中的元素不脱离文档流,而简单地进行样式的变化,比如修改颜色、背景等, 浏览器重新绘制样式 回流:指的是处于文档流中
DOM
的尺寸大小、位置或者某些属性发生变化时,导致浏览器重新渲 染部分或全部文档的情况
相比之下,回流要比重绘消耗性能开支更大。另外,一些属性的读取也会引起回流,比如读取某个 DOM 的高度和宽度,或者使用 getComputedStyle 方法。
JS基础
JS基础知识,规定语法(ECMA262标准)
JSWebAPI,网页操作的API(W3C标准)
前者是后者的基础,两者结合才能真正实际应用
1.class 和 继承
简单的回顾下
class
constructor
属性
方法
注:class是ES6语法
class Student {
constructor(name, age) {
this.name = name
this.number = number
}
skill() {
console.log(`我是${this.name}今年${this.age}我会学习`)
}
}
var zhangsan = new Student('张三', 18)
console.log(zhangsan.name) //张三
console.log(zhangsan.number) //18
zhangsan.skill() //我是张三今年18我会学习
继承
extends
super
扩展或重写方法
class People {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name} 特能吃`)
}
}
class Student extends People {
constructor(name, number) {
super(name)
this.name = name + '【法外狂徒】'
this.number = number
}
skill() {
console.log(`我是${this.name}今年${this.age}我会学习`)
}
}
var zhangsan = new Student('张三', 18)
console.log(zhangsan.name) //张三【法外狂徒】
console.log(zhangsan.number) //18
zhangsan.skill() //我是张三【法外狂徒】今年18我会学习
zhangsan.eat() //张三【法外狂徒】 特能吃
typeof Student
"function"typeof People
"function"由此可见
class
也是function
2.变量类型
原始类型
注意:原始类型不包含 Object。
ECMAScript 中定义了 6 种原始类型:
- Boolean
- String
- Number
- Null
- Undefined
- Symbol(ES6 新定义)
typeof
typeof 可以判断以下几种类型:
- undefined
- boolean
- number
- string
- object
- function
- symbol
注意:
typeof null
结果是object
,实际这是 typeof 的一个bug,null是原始值,非引用类型
typeof 数组
结果是object
,结果中没有array
这一项,引用类型除了function
其他的全 部都是object
typeof Symbol()
用typeof
获取symbol
类型的值得到的是symbol
,这是 ES6 新增的知识点
类型判断 - instanceof
instanceof 是基于原型链查找
zhangsan instanceof Student
//true
zhangsan instanceof People
//true
zhangsan instanceof Object
//true
[] instanceof Array
//true
[] instanceof Object
//true
{} instanceof Object
//true
3.原型
原型
原型就是一个对象,实例“继承”那个对象的属性。在原型上定义的属性,通过“继承”,实例也拥有了这个属性。“继承”这个行为是在 new 操作符内部实现的。
构造函数内部有一个名为 prototype 的属性,通过这个属性就能访问到原型
隐式原型 显示原型
Student
就是构造函数,Student.prototype
就是原型实例通过
__proto__
访问到原型所以这两者是等价的:
zhangsan.prototype
显式原型zhangsan.__proto__
隐式原型zhangsan.prototype
===zhangsan.__proto__
4.原型链
原型链
原型里面也有个__proto__
属性,原型可以通过这个属性访问到原型它自己的原型
举个栗子
Student
的一个实例 zhangsan
,当调用它的一个属性或者方法时,他自身没有这个方法或者属性,它就通过__proto__
来进行访问原型看看原型上有无该属性和方法,有的话就能直接使用,没有就查找原型的__proto__
也就是原型的原型继续找有没有该属性或者方法直到最顶层为null
为止,没有查找到就是不存在
这种不断向上搜索形成的链状关系我们就称为原型链
5.如何准确的判断一个变量是数组
显然通过
typeof []
判断不能得到想要的结果 "object"常用的方法就是instanceof 基于原型链查找的方式进行判断
还有就是通过Object.prototype.toString进行判断
[] instanceof Array
结果 :true
Object.prototype.toString.call([])
结果:"[object Array]"
6.手写简易的jQuery 考虑插件和扩展性
写个简单的html等下使用
<ul>
<li>点我</li>
<li>点我</li>
<li>点我</li>
<li>点我</li>
<li>点我</li>
</ul>
首先先简单的写个jQuery
class jQuery {
// 获取元素进行保存
constructor(selector) {
const result = document.querySelectorAll(selector)
const lenght = result.length
for (let i = 0; i < result.length; i++) {
this[i] = result[i]
}
this.lenght = lenght
this.selector = result
}
// 实现get方法,把jQ变成Dom
get(index) {
return this[index]
}
// 遍历
each(fn) {
for (let i = 0; i < this.lenght; i++) {
const ele = this[i]
fn(ele)
}
}
// 绑定事件
on(type, fn) {
this.each((ele) => {
ele.addEventListener(type, fn, false)
})
return this
}
}
// 添加一个插件
jQuery.prototype.dialog = function () {
alert('这是一个dialog')
return this
}
// 以后使用就是直接使用 $
const $ = (selector) => new jQuery(selector)
// 使用
$('ul li').on('click', function () {
console.log(this)
})
// 调用 添加的方法
$().dialog()
也可以继续造轮子
// 造轮子
class myJQuery extends jQuery {
constructor(selector) {
super(selector)
}
// 在这里扩展自己方法
css(json) {
for (let key in json) {
this.each((ele) => {
ele.style[key] = json[key]
})
}
return this
}
}
// 使用
new myJQuery('ul li').css({
background: 'red',
})
7.作用域和闭包
简单来说,作用域 指程序中定义变量的区域,它决定了当前执行代码对变量的访问权限。
由于作用域的限制,每段独立的执行代码块只能访问自己作用域和外层作用域中的变量,无法访问到内层作用域的变量。
javascript
中大部分情况下,只有两种作用域类型:
- 全局作用域:全局作用域为程序的最外层作用域,一直存在。
- 函数作用域:函数作用域只有函数被定义时才会创建,包含在父级函数作用域 / 全局作用域内。
自由变量
- 一个变量在当前作用域没有定义,但被使用了
- 向上级作用域,一层一层依次寻找,直至找到为止
- 如果到全局作用域都没找到,则报错Xx is not defined
作用域链
如果当前作用域中的使用的自由变量没有定义,就会向外层作用域中寻找,如果外层没有就继续一直寻找下去,如果全局作用域都没有就是没有报错 ReferenceError
这种不断向上搜索形成的链状关系称为作用域链
块级作用域
ES6
标准提出了使用 let
和 const
代替 var
关键字,来“创建块级作用域”
简单来说,花括号内 {...}
的区域就是块级作用域区域。
{
let a = 1;
}
console.log(a); // ReferenceError
作用域的一个常见运用场景之一,就是 模块化。 全局作用域污染和变量名冲突,代码结构臃肿且复用性不高。
闭包: 能够访问其他函数内部变量的函数,被称为 闭包。
是作用域应用的特殊情况
有两种表现:函数作为参数被传递函数 和 作为返回值被返回
应用场景: 大多数是在需要维护内部变量的场景下。
例子一
function test() {
const a = 30
return function print() {
console.log(a)
}
}
const a = 100
test()() //30
例子二
const b = 30
function fun() {
console.log(b)
}
function test2(fn) {
const b = 100
fn()
}
test2(fun) //30
所有的自由变量的查找,是在函数定义的地方,向上级作用域查找 不是在执行的地方!
由于闭包使用过度而导致的内存占用无法释放的情况,我们称之为:内存泄露。
8.this
this取什么值是在函数执行的时候确定不是定义的时候确定
function fn1() {
console.log(this)
}
fn1() //window
fn1.call({ a: 1 }) //{ a: 1 }
fn1.bind({ b: 2 })() //{ b: 2 }
- 普通函数中:this->window
- 定时器中:this->window
- 构造函数中:this->当前实例化的对象
- 事件处理函数中:this->事件触发对象
- 在 js 中一般理解就是谁调用这个 this 就指向谁
9.创建10个 a 标签,点击的时候弹出对应的序号
考察块级作用域
for (let i = 0; i < 10; i++) {
const a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
}
10.实际开发中闭包的应用
隐藏数据 只提供API 保证数据安全
function createCache() {
const data = {}
return {
set(key, val) {
console.log(data, key, val)
data[key] = val
},
get(key) {
return data[key]
},
}
}
const c = createCache()
c.set('a', 'haha ')
console.log(c.get('a')) //haha
11.手写call,apply,bind函数
手写call,apply,bind 比较基础
手写
call
,call
的性能比apply
高
Function.prototype.call1 = function (obj, ...args) {
// 保存传入的this
const context = obj
// 避免覆盖原来的方法,所以就使用 Symbol保证唯一性
const fn = Symbol()
// 把方法添加到传入的this,然后方法指向 call1的调用者this也就是方法
context[fn] = this
// 执行方法
const result = context[fn](...args)
// 删除传入this的fn方法
delete context[fn]
// 返回结果
return result
}
// 方法的使用
function sum(num1, num2) {
console.log(this.base + num1 + num2)
}
let obj = {
base: 1,
}
sum.call1(obj, 100, 200) //301
手写
apply
// 和上面call差不多,第二个参数不用展开了
Function.prototype.apply1 = function (obj, args) {
// 保存传入的this
const context = obj
// 避免覆盖原来的方法,所以就使用 Symbol保证唯一性
const fn = Symbol()
// 把方法添加到传入的this,然后方法指向 call1的调用者this也就是方法
context[fn] = this
// 执行方法
const result = context[fn](...args)
// 删除传入this的fn方法
delete context[fn]
// 返回结果
return result
}
// 方法的使用
function sum(num1, num2) {
console.log(this.base + num1 + num2)
}
let obj = {
base: 1,
}
sum.apply1(obj, [100, 200]) //301
手写
bind
Function.prototype.mybind = function () {
// 将参数转为数组 等价 const args = arguments.slice()
const args = Array.prototype.slice.call(arguments)
// 把传递this取出来
const myThis = args.shift()
// 当前函数的this先保存
const self = this
// 返回一个函数
return function () {
// 执行原函数并返回结果
return self.apply(myThis, args)
}
}
function fn(a, b) {
console.log(this, a + b)
}
fn.mybind({ a: 1 }, 1, 2)() //{a: 1} 3
手写完整的
bind
支持new
Function.prototype.bind = function(OThis, ...outherArgs) {
// 缓存调用的函数
const thatFunc = this
const fBind = function(...innerArgs) {
// 判断是否是new 还是直接执行函数
return thisFunc.apply(
this instanceof thatFunc ? this : OThis, [...outherArgs, ...innerArgs]
)
}
// 考虑不能污染到 thatFunc的prototype 这里直接用 Object.create()
fBind.prototeye = Object.create(thatFunc.prototype)
// 返回一个函数
return fBind
}
例题
function fn1() {
console.log('fn1')
}
function fn2() {
console.log('fn2')
}
fn1.call.call(fn2) // 'fn2'
// 第一次调用call
// context = fn2 ; this = fn1
//return fn2[fn1]() ---> fn2.fn1()
// 第二次调用
// context = fn2 ; this = call
//return fn2[call](window) ---> window.fn2() ---> 'fn2'
fn1.call.call.call.call.call.call.call.call(fn2) // 'fn2'
12.异步
JS
是单线程语言,只能同时做一件事儿浏览器和
nodejs
已支持JS启动进程,如Web Worker
JS
和DOM
渲染共用同一个线程,因为JS
可修改DOM
结构
因为是单线程
遇到等待(网络请求,定时任务)不能卡住,需要异步,采用了回调callback函数形式
同步和异步
- 基于JS是单线程语言
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
//异步
console.log(100)
setTimeout(function() {
console.log(300)
})
console.log(200)
//不会阻塞代码
//同步
console.log(100)
alert(1)//执行在这里就卡住了
console.log(200)
应用场景 :
网络请求, 比如ajax图片加载
定时任务,比如 setTimeout
13.callback hell 回调地狱
比如这个例子,一直往下面嵌套(函数作为参数层层嵌套)
回调地狱最主要的就是因为功能逻辑代码嵌套的层次太多,导致可读性降低,维护困难,避免回调地狱的最重要的方面是将功能移开,保持代码简单
//狱取第一份数据
$.get(url1, (data1) => {
console. log(datal)
//获取第二份数据
$.get(url2,(data2) => {
console. Log(data2)
//获取第三份数据
$.get(url3, (data3) => {
console. log(data3)
//还可能获取更多的数据
})
})
})
14.Promise
Promise:是编写异步代码的一种方式,它仍然以自顶向下的方式执行,并且由于鼓励使用try / catch样式错误处理而处理更多类型的错误
Promise解决了回调地狱嵌套的问题
1.一个简单的Promise例子
function getData(url) {
return new Promise((resolve, reject) => {
$.ajax({
url,
success(data) {
resolve(data)
},
error(err) {
reject(err)
},
})
})
}
const url1 = '/data1.json'
const ur12 = '/data2.json'
const url3 = '/data3.json'
getData(url1)
.then((data1) => {
console.log(data1)
return getData(url2)
})
.then((data2) => {
console.log(data2)
return getData(url3)
})
.then((data3) => {
console.log(data3)
})
.catch((err) => console.error(err))
2.手写用Promise加载一张图片
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = document.createElement('img')
img.onload = function () {
resolve({ img, msg: '图片加载成功' })
}
img.onerror = function () {
reject(new Error(`${img.src}图片加载失败`))
}
img.src = url
})
}
loadImg('./img/2.jpg')
.then((res) => {
console.log(res)
document.body.appendChild(res.img)
})
.catch((res) => {
console.log(res)
})
15.event loop(事件循环)
异步和事件循环的关系
- JS是单线程运行的
- 异步要基于回调来实现
- event loop就是异步回调的实现原理
JS代码是如何执行的
- 从前到后,一行一-行执行
- 如果某一行执行报错,则停止下面代码的执行
- 先把同步代码执行完,再执行异步
console.log('Hi')
setTimeout(function cb1() {
console.Log('cb1') // cb即callback
}, 5000)
console.log('Bye')
// 打印顺序就是 Hi Bye cb1
事件循环过程(还没涉及宏任务/微任务)
- 同步代码,一行一 行放在
Call Stack
执行(同步任务在栈里压栈弹栈执行) - 遇到异步,会先“记录”下,等待时机(定时、网络请求等)
- 时机到了,就移动到
Callback Queue
(回调队列) - 如
Call Stack
为空(即同步代码执行完)Event Loop
开始工作 - 轮询查找
Callback Queue
(回调队列) , 如有则移动到Call Stack
执行 - 然后继续轮询查找(永动机一样)
自己画的图,不对的地方请多多指教
16.Promise状态
三种状态
pending
不会触发then和catchresolved
会触发后续的then回调函数rejected
会触发后续的catch回调函数
状态的表现 和 变化 (变化是不可逆的)
pending
一> resolved
或 pending
一> rejected
then
和catch
对状态的影响(重要)
then
正常返回resolved, 里面有报错则返回rejectedcatch
正常返回resolved,里面有报错则返回rejected
then和catch的链式调用(常考)
网上的promise习题很多的,我这里就编一个简单点的,最后打印的结果
const p1 = Promise.resolve().then(() => {
return 100
})
// p1.then中没有出错,正常返回resolved 触发后续的then回调
p1.then((data) => {
console.log(data) //100
}).catch(() => {
console.error(new Error('err')) //这行代码必然不被执行
})
// rejected执行的是之后的catch回调
const p2 = Promise.reject().then(() => {
throw new Error('then error') //这行代码必然不被执行
})
p2.then(() => {
console.log('200') //这个也不执行
})
.catch(() => {
console.log('300')
throw new Error('err') //返回rejected 执行下面catch
})
.then(() => {
console.log('400')//这个也不执行
})
.catch(() => {
console.log('500') //返回resolved 执行下面then
})
.then(() => {
console.log('600')
})
最后打印 的是 100 300 500 600
网上扒两个简单的
例题1
Promise.resolve()
.then(() => {
console.log(1)
throw new Error('erro1')
})
.catch(() => {
console.log(2)
})
.then(() => {
console.log(3)
})
打印的是 1 2 3
例题2
Promise.resolve()
.then(() => {
console.log(1) // 1
throw new Error('erro1')
})
.catch(() => {
console.log(2)
})
.catch(() => {
console.log(3)
})
打印的是 1 2
网上的例题很多哈,大家有点时候可以多练习
17.async / await
async
/await
和 异步回调callback hell
Promise
then catch链式调用,但也是基于回调函数async
/await
是同步语法,彻底消灭异步回调的终极武器- 但和
Promise
并不互斥 ,反而,两者相辅相成
async
/await
和Promise
的关系
- 执行
async
函数,返回的是Promise对象 await
相当于Promise
的thentry...catch
可捕获异常,代替了Promise
的catch
下面几个例子
!(async function () {
const p1 = Promise.resolve(300)
const data = await p1 // await 相当于Promise then
console.log('data', data)
})()
!(async function () {
const datal = await 400 //await 相当于Promise.resolve(400)
console.log('datal', datal)
})()
function fn1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(500)
}, 1000)
})
}
!(async function () {
const data2 = await fn1()
console.log('data2', data2)
})()
!(async function () {
const p4 = Promise.reject('err') // rejected状态
try {
const res = await p4
console.log(res)
} catch (ex) {
console.error(ex) // try..catch相当于promise catch
}
})()
异步的本质
async
/await
是消灭异步回调的终极武器- JS还是单线程,还得是有异步,还得是基于
event loop
async/await
只是一个语法糖,但这颗糖真香!
结合事件循环做两个例题
例题一
async function async1() {
console.log('async1 start')
await async2()
// await 后面的代码都看做异步回调
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
async1()
console.log('script end')
结果打印的是 :
script start
async1 start
async2
script end
async1 end
例题二
!(async function () {
async function async1() {
console.log('async1 start') //2
await async2()
console.log('async1 end') //4
await async3()
console.log('async1 end 2') //6
}
async function async2() {
console.log('async2') //3
}
async function async3() {
console.log('async3') //5
}
console.log('script start') //1
await async1()
console.log('script end') //7
})()
打印的结果如下:
18.for ... of
for .. in
(以及forEach
for
)是常规的同步遍历
for .. of
常用于异步的遍历用个例子来说明两个的区别
function test(item) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(item * item)
}, 1000)
})
}
const arr = [100, 200, 300]
arr.forEach(async (i) => {
console.log(await test(i))
})
上面的例子,使用同步遍历的时候 是在1000ms后全部把结果打印出来了,而不是隔一秒打印
使用
for ... of
遍历
function test(item) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(item * item)
}, 1000)
})
}
const arr = [100, 200, 300]
!(async function () {
for (let item of arr) {
console.log(await test(item))
}
})()
使用for...of
遍历会 1000ms 打印一次
19.宏任务/微任务
简单介绍:
微任务和宏任务皆为异步任务,它们都属于一个队列,主要区别在于他们的执行顺序
- 宏任务: setTimeout,setInterval,Ajax,DOM事件
- 微任务: Promise async/await
- 微任务执行时机比宏任务要早
先来个简单的demo
console.log(1)
setTimeout(() => {
console.log(2)
}, 1)
Promise.resolve().then(() => {
console.log(3)
})
console.log(4)
结果:1 4 3 2
说下DOM渲染
- JS是单线程的,而且和DOM渲染共用一个线程
- JS执行的时候,得留一-些时机供DOM渲染
- 每次Call Stack(执行栈)清空(即每次轮询结束), 即同步任务执行完
- 都是DOM重新渲染的机会,DOM结构如有改变则重新渲染
- 然后再去触发下一次Event Loop(事件循环)
宏任务: DOM渲染后触发,如
setTimeout
微任务: DOM渲染前触发,如
Promise
微任务是ES6语法规定的
宏任务是由浏览器规定的
在事件循环中
宏任务
/微任务
之间的关系
写两个例题
例题一
async function async1() {
console.log('async1 start')
await async2()
// await 后面作为异步回调 这里是微任务
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
// 宏任务
setTimeout(() => {
console.log('setitmeout')
}, 0)
async1()
结果:
例题二
new Promise((resolve) => {
console.log('promise1')//初始化Promise时会立即执行
resolve()
}).then(() => {
console.log('promise2')//微任务
})
console.log('script end')
结果:
20.DOM节点
DOM 是一种树结构 (DOM树)
节点操作 简单举几个例子,这些基本API不用太多介绍
const div1 = document.getElementById('diV1') / 元素
const divList = document.getElementsByTagName('div') //集合
console.log(divList.length)
console.log(divList[0])
const containerListe = document.getElementsByClassName('container') //集合
const pList = document.querySelectorAlL('p') //集合
DOM
节点的property
const pListe = document.querySelectorAll(p)
const p = pList[0]
console.log(pList[0].style.width) / 获取样式
p.style.width = '100px' //修改样式
console.log(p.className) //获取class
p.className = 'p1' // 修改class
//获取nodeName和nodeType
console.log(p.nodeName)
console.log(p.nodeType)
DOM
节点的attribute
const pListe = document.querySelectorAll(p)
const p = pList[0]
p.setAttribute('data-src', './21.pro.jpg')
console.log(p.getAttribute('data-src'))
p.setAttribute('style', 'color:#ccc;')
console.log(p.getAttribute('style'))
p.removeAttribute("style");
property
:修改对象属性,不会体现到html结构中
attribute
:修改html属性,会改变html结构两者都有可能引起
DOM
重新渲染
21.DOM节点操作
节点操作
const p = document.createElement("p"); //创建DOM元素
p.remove(); // 删除p
p.removeChild(clildEle) // 删除p中的子元素
const p2 = p.cloneNode(true)//克隆
box.appendChild(p);//追加到最后
box.appendChild(p2);//如果p2之前就在DOM里,再次添加到box里面就是移动节点
p.insertBefore(newele,ele) // 在p元素中的 ele元素前插入 newele
box.replaceChild(newEle,oldEle) //替换dom
console.log(p.nodeType); // 判断p的类型
节点之间的联系
-
parentNode 查找父节点
-
childElementCount 返回子元素的个数不包括文本节点和注释
-
firstChild 查找指定节点的第一个字节点
-
lastChild 查找指定节点的最后一个字节点
-
previousSibling 查找指定节点的上一个节点
-
firstElementChild 返回第一个子元素
-
lastElementChild 返回最后一个子元素
-
previousElementSibling 返回前一个相邻兄弟元素
-
nextElementSibling 返回后一个相邻兄弟元素
-
nextSibling 查找指定节点的下一个节点
22.DOM性能
- DOM操作非常"昂贵", 避免频繁的DOM操作
- 对DOM查询做缓存
- 将频繁操作改为一次性操作
DOM 查询做缓存
// 不缓存查询
for (let i = 0; i < document.getElementsByTagName('p').length; i++) {
// 每次循环都要进行查询
}
// 缓存DOM 查询
const pList = document.getElementsByTagName('p')
const length = pList.length
for (let i = 0; i < length; i++) {
// 缓存之后就查询一次
}
将频繁操作改做一次性操作
// 创建一个文档碎片
const fragment = document.createDocumentFragment()
// 插入内容
for (let i = 0; i < 10; i++) {
const img = document.createElement('img')
img.src = './img/2.jpg'
fragment.appendChild(img)
}
// 完成之后,直接把文档片段插入DOM 中
document.body.appendChild(fragment)
23.BOM 简单的过一遍
BOM
的核心对象window
:
window
是全局对象- 浏览器窗口的
JavaScript
接口
navigator
客服端标识浏览器的标准,主要用来记录和检测浏览器与设备的主要信息
如何识别浏览器?
const ua = window.navigator.userAgent
const isChrome = ua.indexOf('Chrome')
console.log(isChrome)
screen
保存客服端能力的对象 举几个常见的属性
height
: 屏幕像素高度left
: 当前屏幕左边的像素距离top
: 当前屏幕顶端的像素距离width
: 屏幕像素宽度
location
可以用location
拆解url
protocol
:协议host
:域名和端口hostname
:服务器名pathname
:url
路径hash
:哈希值href
:url
字段search
:查询字段
history
go()
:跳转页面forward
:前进back()
:后退
24.事件
事件绑定 简单介绍下,相信大家都会
let btn = docment.getElementById('btn')
//用on绑定
btn.onclick = funcition(){
//事件处理
}
//使用addEventListener
btn.addEventListener('click',function(){
//事件处理逻辑
},false)
简述事件冒泡的流程
- 基于DOM树形结构
- 事件会顺着触发元素网上冒泡
- 应用场景:事件代理
事件代理的好处
- 代码简洁
- 减少浏览器内存占用
- 但是,不要滥用
- 对于那些新添加的元素,最好就使用事件代理
例子 : 无限下拉图片列表,如何监听每个图片的点击
- 事件代理
- 用e.target获取触发元素
- 用matches来判断是否是触发元素
自己封装通用的事件绑定函数
Object.prototype.bindEvent = function (type, selector, fn) {
// 如果第三个参数为空说明不是事件代理
if (fn == null) {
fn = selector
selector = null
}
this.addEventListener(type, (e) => {
// 保存事件的触发元素
const target = e.target
// 事件代理
if (selector) {
if (target.matches(selector)) {
fn.call(target, e)
}
} else {
// 普通事件绑定
fn.call(target, e)
}
})
}
const ul = document.querySelector('ul')
// 普通使用
ul.bindEvent('click', function () {
console.log(this)
})
// 事件代理
ul.bindEvent('click', 'li', function () {
console.log(this)
})
25.ajax
手写一个简单的ajax
get请求
const xhr = new XMLHttpRequest()
// get请求
xhr.open('GET', './test.json', true)
xhr.onreadystatechange = function () {
if (xhr.status === 200 && xhr.readyState === 4) {
console.log(xhr.responseText)
}
}
xhr.send(null)
post请求
const xhr = new XMLHttpRequest()
// post 请求
xhr.open('POST', 'localhost://mytest', true)
xhr.onreadystatechange = function () {
if (xhr.status === 200 && xhr.readyState == 4) {
console.log(xhr.responseText)
}
}
xhr.send(
JSON.stringify({
name: 'zhangsan',
age: 18,
})
)
状态码
xhr.readyState
- 0- (未初始化)还没有调用send(方法
- 1- (载入)已调用send(方法,正在发送请求
- 2- (载入完成) send()方法执行完成,已经接收到全部响应内容
- 3- (交互)正在解析响应内容
- 4- (完成)响应内容解析完成,可以在客户端调用
响应码
xhr.status
- 2xx -表示成功处理请求,如200
- 3xx -需要重定向,浏览器直接跳转,如301 302 304
- 4xx -客户端请求错误,如404 403
- 5xx -服务器端错误
26.跨域
什么事跨域
简单来说:由于同源策略的原因浏览器不能执行其他网站的脚本
所有的跨域,都必须经过server端允许和配合
未经server端允许就实现跨域,说明浏览器有漏洞,危险信号
同源政策
- ajax请求时,浏览器要求当前网页和server必须同源(安全)
- 同源:协议、域名、端口,三者必须一-致
- 前端: a.com:8080/ ; server :b.com/api/xx(不同源)
加载图片 、css、 js可无视同源策略
<img src=跨域的图片地址/>
可用于统计打点,可使用第三E方统计服务<link href=跨域的css地址/>
可使用CDN , CDN一般都是外域<script src=跨域的js地址> </script>
可使用CDN,可实现JSON
jsonp
因为script标签可以发送get请求,比如引用CDN,我们可以动态的创建script标签,再去请求一个带参网址来实现跨域通信
- script标签可绕过跨域限制
- 服务器可以任意动态拼接数据返回
- 所以,
<script>
就可以获得跨域的数据,只要服务端愿意返回
写个简易jsonp的例子
后台是
node.js
+express
app.get('/info',(req,res)=>{
const result = 'fn({name : '张三'})'
//返回fn({name : '张三'})并执行
res.send(result) //............3
})
前端
<script>
//首先定义好一个fn函数 ............1
window.fn = function (data) {
//打印获取到的数据
console.log(data) //...........4
}
</script>
//使用script标签发送jsonp请求 ..............2
<script src="http://localhost:8080/mytest/info"></script>
当然自己也可以自己封装一个jsonp函数
function jsonp(options) {
// 动态创建script
const script = document.createElement('script')
// 拼接字符串变量
let params = ''
// 拼接多个参数
for (let attr in options.data) {
params += '&' + attr + '=' + options.data[attr]
}
// 随机生成函数名
const fnName = 'myJsonp' + Math.random().toString().replace('.', '')
// 把fnName变成全局函数
window[fnName] = options.success
// 给script添加src属性
script.src = options.url + '?callback=' + fnName + params
// script添加到页面中
document.body.appendChild(script)
// 触发后移除script
script.onload = function () {
document.body.removeChild(this)
}
}
// 使用
btn.onclick = function () {
jsonp({
url: 'url地址',
success: function (data) {
console.log(data)
},
})
}
jQuery实现jsonp
$.ajax({
url: 'url地址',
//必须指定为jsonp
dataType: 'jsonp',
//自定义的回调函数名称,默认值为jqueryxxx 格式
jsonpCallback: 'callback',
success: function (data) {
console.log(data)
},
})
默认情况下,使用jQuery
发起JSONP
请求,会自动携带一个 callback=jQueryxxx
的参数, jQuery
是随机生成的一个回调函数名称。
CORS
-服务器设置http header
//允许客户端额外向服务器发送Content-Type 请求头和Xx-Custom-Header 请求头
//注意:多个请求头之间使用英文的逗号进行分割
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header')
//只允许POST、 GET、 DELETE、 HEAD请求方法
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, HEAD')
//允许所有的HTTP 请求方法
res.setHeader('Access-Control-Allow-Methods', '*')
27.ajax工具
jQuery中封装的$.ajax就不多说了
Fetch:简单的例子 默认GET请求
fetch('url地址')
.then(data => console.log(data))
.catch(err => console.log(err));
Fetch底层是用Promise实现,我们可以直接用async来优化上面的代码,减少回调,使其更加语义化、容易理解
!(async function(){
try{
const res = await fetch('url地址')
console.log(res)
}catch(err){
console.log(err)
}
})()
Fetch的POST请求
fetch(url, {
method: 'post',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
},
body: 'name="zhangsan"&age=18'
})
.then(function (data) {
console.log(data);
})
.catch(function (err) {
console.log(err);
});
带上cookie需要设置credentials
fetch(url, {
credentials: 'include'
})
axios的基本用法应该不必多说了。大家基本天天用吧
28.存储
描述
cookie
localStorage
sessionStorage
区别
cookie
- 本身用于浏览器和
server
通讯 - 在
html5
之前 被"借用”到本地存储来 - 可用
document.cookie =
来修改
cookie
缺点
- 存储大小,最大4KB
http
请求时需要发送到服务端,增加请求数据量- 只能用
document.cookie =
来修改,太过简陋
localStorage
和sessionStorage
HTML5
专门为存储而设计,最大可存5M- API简单易用
setItem
getItem
- 不会随着
http
请求被发送出去 localStorage
数据会永久存储,除非代码或手动删除sessionStorage
数据只存在于当前会话,浏览器关闭则清空- 一般用
localStorage
会更多一-些
29.HTTP
状态码分类
- 1xx服务器收到请求
- 2xx请求成功,如200
- 3xx重定向,如302
- 4xx客户端错误,如404
- 5xx服务端错误,如500
常见状态码
200
成功301
永久重定向(配合location
,浏览器自动处理)302
临时重定向(配合location
,浏览器自动处理)304
资源未被修改404
资源未找到403
没有权限500
服务器错误504
网关超时
关于协议和规范
- 就是一个约定
- 要求大家都跟着执行
- 不要违反规范,例如IE浏览器
http headers
Request Headers
- Accept浏览器可接收的数据格式
- Accept-Encoding浏览器可接收的压缩算法,如gzip
- Accept-Languange浏览器可接收的语言,如zh-CN
- Connection: keep-alive - -次TCP连接重复使用
- cookie
- Host
- User-Agent (简称UA )浏览器信息
- Content-type发送数据的格式,如application/json
Response Headers
- Content-type返回数据的格式,如application/json
- Content-length返回数据的大小,多少字节
- Content-Encoding返回数据的压缩算法,如gzip
- Set-Cookie
自定义
header
了解下
header : {
'X-Requested-with': 'XMLHttpRequest'
}
30.HTTP缓存
关于缓存
- 减少带宽,减少请求数量,减轻服务器压力,提升性能
- 浏览器缓存一般都是针对静态资源,比如 js、css、图片 等
强制缓存
如果过期了 再次请求 ,返回Cache-Control
Cache-Control
Response Headers
中 控制强制缓存的逻辑例如
Cache-Control
:max-age=31536000
( 单位是秒)
- public:资源客户端和服务器都可以缓存。
- privite:资源只有客户端可以缓存。
- no-cache:客户端缓存资源,但是是否缓存需要经过协商缓存来验证。
- no-store:不使用缓存。
- max-age:缓存保质期。
关于
Expires
- 同在
Response Headers
中 - 同为控制缓存过期
- 已被
Cache-Control
代替
协商缓存
- 服务器端缓存策略
- 服务器判断客户端资源,是否和服务端资源一样
- 致则返回304 , 否则返回200和最新的资源
资源标识
- 在
Response Headers
中,有两种 Last-Modified
资源的最后修改时间Etag
资源的唯一标识( -一个字符串,类似人类的指纹)
下面是一个
Headers
实例
Last-Modified
和Etag
- 会优先使用
Etag
Last-Modified
只能精确到秒级- 如果资源被重复生成,而内容不变,则
Etag
更精确
缓存流程图
三种刷新操作对缓存的影响
正常操作:地址栏输入url
,跳转链接,前进后退等
手动刷新: F5
,点击刷新按钮,右击菜单刷新
强制刷新: ctrl + F5
- 正常操作:强制缓存有效,协商缓存有效
- 手动刷新:强制缓存失效,协商缓存有效
- 强制刷新:强制缓存失效,协商缓存失效
关于开发环境
1.git
最常用的代码版本管理工具
大型项目需要多人协作开发,必须熟用git
如果你不知道或者之前不用git ,不会通过面试
- Mac OS自带git命令,windows可去官网下载安装
- git服务端常见的有github coding.net等
- 大公司会搭建自己的内网git服务
我这里就不用多多介绍了,我就说下我们经常用的操作
第一次使用git,配置用户信息
- 用户名:
git config --global user.name "用户名"
- 户邮箱:
git config --global user.email "邮箱地址"
初始化仓库
- 创建一个新的本地仓库:
git init
- 或者从远程git仓库复制项目:
git clone <远程git仓库url地址>
添加到暂存区
- 添加指定文件:
git add 文件1 文件2 ...
- 添加文件夹:
git add [文件夹]
- 添加所有文件:
git add .
撤销
- 取消暂存区已经暂存的文件:
git reset HEAD 文件1 ...
- 隐藏当前变更,以便能够切换分支:
git stash
; - 如果代码写错到了分支先
git stash
切换分支后 就git stash pop
释放刚才的内容
重命名文件
- 并提交到暂存区:
git mv [old] [new]
查看信息
- 查询当前工作区所有文件的状态:
git status
- 比较当前修改的文件 和 暂存区的 差异:
git diff 文件
- 直接比较有修改的所有的文件:
git diff
提交
提交到本地仓库:git commit -m "提交信息"
撤销上次提交:git commit --amend
跳过暂存区直接提交,自己不用add .
:git commit -a -m "提交信息"
分支管理
- 显示所有分支:
git branch
- 创建分支:
git branch <新分支名称>
- 新建子分支:
git checkout -b <子分支名称>
- 切换到其他分支:
git checkout <分支名称>
- 当前分支与其他分支合并:
git merge <分支名称>
- 把远程分支合并到当前分支:
git merge <远程分支>/<本地分支>
- 删除分支:
git branch -d <分支名称>
- 添加远程仓库:
git remote add [remote-name] [url]
比如github这个例子git remote add origin url
- 将本地仓库某分支推送到远程仓库上:
git push [远程仓库] [分支名字]
这里举个例子git push origin master
2.抓包
移动端h5页,查看网络请求,需要用工具抓包
windows
一般用fiddler
Mac OS
- -般用charles
抓包+调试的工具
spy-debugger
- 手机和电脑连同-一个局域网
- 将手机代理到电脑上
- 手机浏览网页,即可抓包
具体使用方法大家可以去看看其他大佬的帖子,他们讲的非常的好
推荐使用 spy-debugger
支持HTTP/HTTPS
,无需USB连接设备
3.Webpack 和 babel 简单使用
为啥使用Webpack
-
ES6模块化,浏览器暂不支持
-
ES6语法,浏览器并不完全支持
-
压缩代码,整合代码,以让网页加载更快
安装配置Webpack
安装
npm i -D webpack webpack-cli
安装本地项目模块
npm install webpack webpack-cli -D
配置
webpack.config.js
const path = require('path')
module.exports = {
mode: 'development',// 生产环境production
entry: path.join(__dirname, '/src','index.js'), // 入口文件
// 输出文件地址 和 名称 配置
output: {
path: path.join(__dirname, '/dist'),
filename: 'bundle.js'
},
}
配置
package.json
中script
之后就可以使用npm run build
来运行该脚本命令
{
"scripts" : {
"build": "webpack"
}
}
配置本地服务器
安装本地服务器 npm install webpack-dev-server -D
配置
webpack.config.js
const path = require('path')
module.exports = {
mode: 'development',// 生产环境production
entry: path.join(__dirname, '/src','index.js'), // 入口文件
// 输出文件地址 和 名称 配置
output: {
path: path.join(__dirname, '/dist'),
filename: 'bundle.js'
},
devServer: {
contentBase: path.join(__dirname, "dist"),
hot: true,
port: '8080',
inline: true,
open: true,
overlay: true,
compress: true
}
}
- inline:文件改变时会自动刷新
- open:第一次自动打开网页。
- compress:启用 gzip 压缩,boolean 类型,默认为 false
- overlay:页面上报错信息是否显示,默认为false
- port: 端口
插件
配置
package.json
中script
终端输入npm run dev
运行服务器
{
"scripts" : {
"build": "webpack"
"dev": "webpack-dev-server"
}
}
添加个html的压缩 安装
npm install html-webpack-plugin -D
const path = require('path')
//导入 html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',// 生产环境production
entry: path.join(__dirname, '/src','index.js'), // 入口文件
// 输出文件地址 和 名称 配置
output: {
path: path.join(__dirname, '/dist'),
filename: 'bundle.js'
},
devServer: {
contentBase: path.join(__dirname, "dist"),
hot: true,
port: '8080',
inline: true,
open: true,
overlay: true,
compress: true
},
//配置插件
plugin: {
new HtmlWebpackPlugin({
//模板地址
template: path.join(__dirname, "src","index.html"),
//输出名
filename: 'index.html'
})
}
}
这样就能打包html了
加载器
loaders
配置css-loader
安装
npm install style-loader css-loader -D
再去
webpack.config.js
设置 这样就能打包css
module.exports = {
module: {
rules: [
{
test: /\.css$/, // 正则匹配css
use: ['style-loader', 'css-loader'] // 需要用的loader,从左到右的编译
}
]
}
}
babel
安装:npm install @babel/core @babel/preset -env babel -loader -D
项目的目录添加一个.babeirc文件
{
"presets": ["@babel/preset-env"]
}
再去
webpack.config.js
设置
module.exports = {
module: {
rules: [
{
test: /\.js$/, // 正则匹配js
use: ['babel-loader'],
include: path.join(__dirname,'src'),
//除了node的模块
exclude: /node_modules/
}
]
}
}
配置生产环境
项目根目录创建一个 webpack.prod.js
把开发环境的内容复制过来改改
更改mode
为production
输出文件加上bundle.[contenthash].js
const path = require('path')
//导入 html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'production',// 生产环境
entry: path.join(__dirname, '/src','index.js'), // 入口文件
// 输出文件地址 和 名称 配置
output: {
path: path.join(__dirname, '/dist'),
filename: 'bundle.[contenthash].js'
},
//配置插件
plugin: {
new HtmlWebpackPlugin({
//模板地址
template: path.join(__dirname, "src","index.html"),
//输出名
filename: 'index.html'
})
}
}
更改
package.json
中script
中的build
为刚才新建的生产环境webpack.prod.js
使用
npm run build
运行打包
{
"scripts" : {
"build": "webpack"
"dev": "webpack-dev-server"
"build": "webpack --config webpack.prod.js"
}
}
4.ES6模块化规范
ES6的模块化之前有三种规范:CommonJS、AMD、CMD 这几个其他大佬都有详细介绍
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。
我这里就是简单的简述下ES6中的模块化规范
ES6中的模块自动采用严格模式
ES6模块中的顶层
this
指向undefined
export
exprot
一定要写在最顶层 导出的值是动态绑定的,不是缓存
下面演示的导出皆在 index.js
中
分别导出:
export let a = 'a';
export const b = 'b'
export const c = ()=>{ console.log(1) }
一次全部导出
let a = 'a';
const b = 'b'
const c = ()=>{ console.log(1) }
export { a, b, c }
在导出的时候通过 as 取别名
let a = 'a';
const b = 'b'
const c = ()=>{ console.log(1) }
export { a as lang1, b as lang2, c as lang3 }
import
通过结构赋值 获取 导出的元素
import { a, b, c} from './index.js'
console.log(a)
当然导入的时候也可以取别名
import { a as lang } from './index.js'
console.log(lang)
导入所有的元素
import * as lang from './index.js'
console.log(lang.a)
export default
可以指定模块默认输出
//index.js
export default function(){
console.log('123')
}
import导入的元素可以为任意名称
import lang from './index.js'
总结
- 输出单个值,使用
export default
- 输出多个值,使用
export
export default
与普通的export
不要同时使用
5.Linux命令
这里我就摆几个常用的,因为这个是我的总结,自己翻出来看看,所以更全Linux请到其他大佬那里查看
cp
拷贝文件 :cp -rvf a/ /tmp/
mkdir
创建目录:mkdir dir
ls
当前目录的所有内容。
ls -l
目录下详细信息
pwd
当前所在的目录
cd
打开目录
find
查找文件
cat
查看文件内容
grep
对内容进行过滤
diff
命令用来比较两个文件是否的差异
.tar
gz
使用tar ,gzip命令操作
ps
命令能够看到进程/线程状态
ifconfig
查看ip地址
ping
测试网络
关于运行环境
运行环境即浏览器(
server
端有node.js
)下载网页代码,渲染出页面,期间会执行若干JS
要保证代码在浏览器中: 稳定且高效
1.页面加载的过程
1.从输入url到渲染出页面的整个过程
大致的流程
DNS
解析:域名->IP
地址- 浏览器根据
IP
地址向服务器发起http
请求 - 服务器处理
http
请求,并返回给浏览器 - 浏览器解析渲染页面
- 连接结束
渲染过程
- 根据
HTML
代码生成DOM Tree
- 根据
CSS
代码生成CSSOM
- 将
DOM Tree
和CSSOM
整合行程Render Tree
- 根据
Render Tree
渲染页面 - 遇到
<script>
则暂停渲染,优先加载并执行JS
代码,完成再继续 - 直至把
Render Tree
渲染完成
2.window.onload和DOMContentLoaded的区别
window.onload
资源全部加载完才能执行,包括图片DOMContentLoaded
DOM
渲染完成即可,图片可能尚未下载
2.性能优化
是一个综合性问题,没有标准答案,但要求尽量全面
某些细节问题可能会单独提问:手写防抖、节流
只关注核心点,针对面试
性能优化原则
- 多使用内存、缓存或其他方法
- 减少
CPU
计算量,减少网络加载耗时 - (适用于所有编程的性能优化一空间换时间 )
让加载更快
- 减少资源体积:压缩代码
- 减少访问次数:合并代码(多个js文件合并到一个文件然后加载),
SSR
服务器端渲染,缓存 - 使用更快的网络:
CDN
CSS
放在head
,JS
放在body
最下面- 尽早开始执行
JS
,用DOMContentLoaded
触发 - 懒加载(图片懒加载,上滑加载更多)
让渲染更快
- 对DOM查询进行缓存
- 减少频繁DOM操作,合并到一起插入DOM结构
- 节流
throttle
防抖debounce
缓存
- 静态资源加
hash
后缀,根据文件内容计算hash
- 文件内容不变,则
hash
不变,则url
不变 url
和文件不变,则会自动触发http
缓存机制,返回304
SSR
- 服务器端渲染:将网页和数据一起加载,一起渲染
- 非
SSR
(前后端分离) :先加载网页,再加载数据,再渲染数据 - 早先的
JSP ASP PHP
,现在的vue React SSR
防抖
监听一个输入框的,文字变化后触发change事件
直接用keyup事件,则会频发触发change事件
防抖:用户输入结束或暂停时,才会触发change事件
//封装一个防抖的函数
function debounce(fn, delay = 500){
let timer = null
//如果还存在一个定时器没有执行,就清空定时器,在下面重新开启定时器
if(timer){
cleartTimout(timer)
}
timer = seTimeout(()=>{
fn.apply(this,arguments)
timer = null
},delay)
}
//调用
const input = document.getElementById('input')
input.addEventListener('keyup',debounce(()=>{
console.log(input.value)
},600),false)
节流
拖拽一个元素时,要随时拿到该元素被拖拽的位置
直接用
drag
事件,则会频发触发,很容易导致卡顿节流:无论拖拽速度多快,都会每隔
100ms
触发一次
//封装一个防抖的函数
function throttle(fn, delay = 500){
let timer = null
//如果还存在一个定时器没有执行就阻止向下执行,减少操作
if(timer){
return
}
timer = seTimeout(()=>{
fn.apply(this,arguments)
timer = null
},delay)
}
//调用
const img = document.getElementById('img')
img.addEventListener('drag',throttle( function(e){
console.log(e.offsetX,e.offsetY)
},600),false)
3.安全
XSRF
攻击 这里简单的 介绍下
- 你正在购物,看中了某个商品,商品id是100
- 付费接口是xxx.com/pay?id=100 , 但没有任何验证
- 我是攻击者,我看中了一一个商品,id是200
- 我向你发送一封电子邮件 ,邮件标题很吸引人
- 但邮件正文隐藏着<img src=xxx.com/pay?id=200 />
- 你一查看邮件,就帮我购买了id是200的商品
XSRF
预防
- 使用post接口
- 增加验证,例如密码、短信验证码、指纹等
关于一些真题
+ 号
- 两个操作数如果是
number
则直接相加出结果 - 如果其中有一个操作数为
string
, 则将另一个操作数隐式的转换为string
,然后进行字符串拼接得出结果 - 如果操作数为对象或者是数组这种复杂的数据类型,那么就将两个操作数都转换为字符串,进行拼接
- 如果操作数是像
boolean
这种的简单数据类型,那么就将操作数转换为number
相加得出结果 []+{}
因为会被强制转换为",然后+运算符链接- 个{}
,{ }
强制转换为字符串就是"[Object Object]"
{}
当作一个空代码块,+0
是强制将[]转换为number
,转换的过程是+[0] => +"" =>0
最终的结果就是0
var和let const的区别
var
是ES5语法,let
const
是ES6语法;var
有变量提升var
和let
是变量,可修改;const是常量,不可修改;let
const
有块级作用域,var
没有
typeof返回哪些类型
一共是7种 值得注意的一点就是 typeof null //object 前面JS基础有提到
- string
- number
- boolean
- object
- function
- undefined
- symbol(ES6)
列举强制类型转换和隐式类型转换
- 强制:
parseInt
parseFloat
toString
等 - 隐式:
if
、逻辑运算
、==
、+拼接字符串
手写深度比较,模拟lodash isEqual
这里没考虑
function
// 判断对象是否是对象还是数组
function isObject(obj) {
return typeof obj === 'object' && obj !== null
}
// 判断全相等
function isEqual(obj1, obj2) {
// 判断不是对象数组的直接比较值
if (!isObject(obj1) || !isObject(obj2)) {
return obj1 === obj2
}
// 防止传入的 两个是同一对象 直接返回
if (obj1 === obj2) {
return true
}
// 是对象和数组(不考虑function)
// 分别取出keys
const obj1Keys = Object.keys(obj1)
const obj2Keys = Object.keys(obj2)
// 先判断keys的个数相等不
if (obj1Keys.length !== obj2Keys.length) {
return false
}
// 以obj1为基准 递归判断 obj2
for (let key in obj1) {
const result = isEqual(obj1[key], obj2[key])
if (!result) return false
}
// 遍历 递归后到了这里就是全相等
return true
}
//使用
// 传入普通
console.log(isEqual('123', '123')) //true
// 传入一样
console.log(isEqual(obj1, obj1)) //true
// 比较两个一样的
const obj1 = {
x: 1,
y: 2,
s: {
x: 1,
y: 2,
},
}
const obj2 = {
x: 1,
y: 2,
s: {
x: 1,
y: 2,
},
}
console.log(isEqual(obj1, obj2)) //true
split()和join()的区别
他们是一个相反的操做
[1,2,3].join('-') //'1-2-3'
'1-2-3'.split('-') //[1,2,3]
数组的pop push unshift shift分别做什么
一般从这三个角度回答
功能是什么?
返回值是什么?
是否会对原数组造成影响?
const arr = [10, 20, 30, 40]
// pop
const popRes = arr.pop()
console.log(popRes, arr) //40 [10, 20, 30] 返回的是删除的最后一个元素,会改变原数组
// push
const pushRes = arr.push(50) //在数组最后追加 50
console.log(pushRes, arr) //4 [10, 20, 30 ,50] 返回 length 会改变原数组
// unshift
const unshiftRes = arr.unshift(5) //在数组最前追加 5
console.log(unshiftRes, arr) //5 [5, 10, 20, 30 ,50] 返回 length 会改变原数组
// shift
const shiftRes = arr.shift() //删除第一个元素
console.log(shiftRes, arr) //5 [10, 20, 30, 50] 返回的是删除的第一个元素,会改变原数组
扩展
纯函数
- 不改变源数组( 没有副作用)
- 返回一个数组
const arr = [10, 20, 30, 40]
// concat
const arr1 = arr.concat([50, 60, 70])
console.log(arr, arr1) //[10, 20, 30, 40] [10, 20, 30, 40, 50, 60, 70]
// map
const arr2 = arr.map(num => num * 10)
console.log(arr, arr2) //[10, 20, 30, 40] [100, 200, 300, 400]
// filter
const arr3 = arr.filter(num => num > 25)
console.log(arr, arr3) //[10, 20, 30, 40] [30, 40]
// slice 没有参数 在这里是复制了一份数组
const arr4 = arr.slice()
console.log(arr, arr4) //[10, 20, 30, 40] [10, 20, 30, 40]
非纯函数
push
pop
shift
unshift
forEach
some
every
reduce
数组slice和splice的区别
- 功能区别(
slice
-切片,splice
-剪接) - 参数和返回值
- 是否纯函数?
const arr = [10, 20, 30, 40, 50]
// slice 纯函数 原数组不变
const arr1 = arr.slice() //复制 [10, 20, 30, 40, 50]
const arr2 = arr.slice(1, 4) //按照范围截取 取得到左边取不到右边 [20, 30, 40]
const arr3 = arr.slice(2) //截取索引之后的 [30, 40, 50]
const arr4 = arr.slice(-3) //反向截取 [30, 40, 50]
// splice 非纯函数 会改变原数组
const spliceRes = arr.splice(1, 2, 'a', 'b', 'c')
//这里简单举个例子,重点是它改变了原数组
//从索引1开始截取,截取2个元素,并使用 'a', 'b', 'c'替换了之前的截取的空位
console.log(spliceRes, arr) //[20, 30] [10, 'a', 'b', 'c', 40, 50]
[10, 20, 30].map(parseInt)返回结果是什么?
map
的参数和返回值parseInt
参数和返回值
const res = [10,20,30].map(parseInt)
console.log(res) //[10, NaN, NaN]
//拆解
[10,20,30].map((item,index)=>{
return parseInt(item,index)
})
//parseInt 第二个参数可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。
ajax请求get和post的区别?
get
一般用于查询操作,post
一般用户提交操作get
参数拼接在url上,post
放在请求体内(数据体积可更大)- 安全性:
post
易于防止CSRF
函数bind,call和apply的区别
bind
创建一个新的函数, 当被调用时 ,第一参数this值,在调用新函数时,接受的是若干个参数列表
call
方法调用一个函数, 第一参数this值,接受的是若干个参数列表
apply
方法调用一个函数, 第一参数this值,第二个参数是一个数组(或类似数组的对象)
function fn(){
console.log(this)
}
const newFn = fn.bind(this,p1,p2,p3)
newFn()
fn.call(this,p1,p2,p3)
fn.apply(this,arguments)
事件代理(委托)是什么?
事件代理就是利用事件冒泡或事件捕获的机制把一系列的内层元素事件绑定到外层元素。
在上面JS基础24有写
闭包是什么,有什么特性?有什么负面影响?
- 能够访问其他函数内部变量的函数,被称为 闭包
- 回顾作用域和自由变量
- 回顾闭包应用场景:作为参数被传入,作为返回值被返回
- 回顾:自由变量的查找,要在函数定义的地方(而非执行的地方)
- 影响:变量会常驻内存,得不到释放。闭包不要乱用
如何阻止事件冒泡和默认行为?
- event.stopPropagation()
- event.preventDefault()
查找、添加、删除、移动DOM节点的方法?
在上面 JS基础 20.DOM节点 21.DOM节点操作 有介绍
如何减少DOM操作?
- 缓存
DOM
查询结果 - 多次
DOM
操作,合并到一-次插入
解释jsonp的原理,为何它不是真正的ajax ?
jsonp
不是官方的,script
标签实现的请求。jsonp
是一个同步请求,只支持get
请求ajax
通过xhr
对象去实现,支持get post
,跨域请求需要后端配合解决跨域响应头
document load和ready的区别
ready
,表示文档结构已经加载完成(不包含图片等非文字媒体文件)onload
,指示页面包含图片等文件在内的所有元素都加载完成
==
和===
的不同
- 双等:判断值相等,会自动转换数据类型
- 三等:不会自动转换数据类型;全等是先判断左右两边的数据类型,如果数据类型不一致,则直接返回false,之后才会进行两边值的判断
函数声明和函数表达式的区别
- 函数声明
function fn() {...}
- 函数表达式
const fn = function() {..}
- 函数声明会在代码执行前预加载,而函数表达式不会
new Object(和Object.create()的区别
{}
等同于new Object()
, 原型Object.prototypeObject.create(nul)
没有原型Object.create(..)
可指定原型
关于this的场景题
这是一个简单的例子 this 是在执行的时候确定的
const User = {
count : 1,
getCount: function() {
return this.count
}
}
console.log(User.getCount()) //what?
const func = User.getCount
console.log( func() ) //what?
第一个打印的是 1
第二个打印的是 undefined
因为这个时候 this
指向是window
关于作用域和自由变量的场景题
let i
for(i = 1; i <= 3; i++) {
setTimeout(function (){
console.log(i)
},0)
}
//what?
这里打印的是 4 4 4
判断字符串以字母开头,后面字母数字下划线,长度6-30
这里是考察正则
var reg=/^[a-zA-Z]\w{5,29}$/
str.test(reg)
手写字符串trim方法,保证浏览器兼容性
String.prototype.trim = function(){
return this.replace(/^\s+/,'').replace(/\s+$/,'')
}
原型, this, 正则表达式 把空白变为空字符串
如何获取多个数字中的最大值
自己封装max
function max() {
const nums = Array.prototype.slice.call(arguments)//把参数变为数组
let max = 0
nums.forEach(item => {
if(item > max) {
max = item
}
})
return max
}
//使用
console.log(max(1,2,3,4,5)) //5
使用Math.max
console.log(Math.max(1,2,3,4,5)) //5
如何用JS实现继承?
原型链继承
function People() {
this.name = 'lang'
this.age = 21
}
People.prototype.eat = function () {
console.log(this.name + '吃饭')
}
function Student() {}
//关键
Student.prototype = new People()
let lang = new Student()
lang.eat() //lang 吃饭
缺点:
多个实例对引用类型的操作会被篡改
借用构造函数继承
使用call直接 把子类的this 传到父类
function People(name, age) {
this.name = name
this.age = age
this.dd = function () {
console.log(this.name + '打车')
}
}
People.prototype.eat = function () {
console.log(this.name + '吃饭')
}
function Student(name, age) {
// 直接偷
People.call(this, name, age)
}
let lang = new Student('lang', 21)
console.log(lang.name,lang.age) //lang 21
lang.eat() //报错
lang.dd() //报错
优点
- 可以传递参数
- name,age的这些属性是添加在 实例自身上的不是共享
缺点
- 使用不了 父类引用的变量 比如上面的
dd
- 使用不了
prototype
里面的 变量比如eat
组合继承
把 原型链继承 + 借用构造函数继承 组合起来
function People(name, age) {
this.name = name
this.age = age
this.dd = function () {
console.log(this.name + '打车')
}
}
People.prototype.eat = function () {
console.log(this.name + '吃饭')
}
function Student(name, age, sex) {
// 直接偷构造
People.call(this, name, age)
// 顺便给自己添加个属性
this.sex = sex
}
// 原型链继承
Student.prototype = new People()
// 添加子类的方法
Student.prototype.see = function () {
console.log(this.name + '能看见千里之外的景物')
}
let lang = new Student('lang', 21, '男')
lang.eat() //lang吃饭
lang.dd() //lang打车
console.log(lang.sex, lang.name, lang.age) //男 lang 21
lang.dd = function () { //修改的是自己实例的方法,并不能影响到其他实例
console.log('更改过了')
}
lang.dd() //更改过了
let jie = new Student('jie', 21, '男')
jie.dd() //jie打车
jie.see() //jie能看见千里之外的景物
优点
- 可以传递参数
- 父类引用的属性不被共享
- 可以使用父类
prototype
的属性
缺点
- 第一次的继承原型链的时候添加了
name
和age
属性 - 第二次new 子类的实例的时候又添加
name
和age
属性 - 根据原型链的查找规则 第二次会把第一次的覆盖
原型式继承
这个是真的用的少 ES5中的
Object.create()
可以实现下面的object
函数
// 处理站
function object(obj) {
//一个空的构造
function Fun() {}
//原型指向传入的对象
Fun.prototype = obj
//返回实例
return new Fun()
}
let people = {
name: 'lang',
age: 18,
hobbys: ['book', 'music', 'learn'],
eat: function () {
console.log(this.name + '吃饭')
},
}
let lang = object(people)
lang.name = 'lang'
lang.hobbys.push('sing')
lang.eat()
let jie = object(people)
jie.name = 'jie'
jie.hobbys.push('play')
jie.eat()
//这里就出了问题,因为是浅复制所有 引用属性被共享了
console.log(lang.hobbys) // ["book", "music", "learn", "sing", "play"]
缺点
- 不能传参
- 因为是浅复制,引用变量被共享了
寄生式继承
创建一个函数,在内部做了一些加强浅复制就是 新增属性和方法,以增强函数,最后返回构造
这里直接用 Object.create() 来实现了
function createObj (obj) {
var clone = Object.create(obj);
//增强操作
clone.see = function () {
console.log('see');
}
clone.haha = 'haha'
return clone;
}
let people = {
name: 'lang',
age: 18,
hobbys: ['book', 'music', 'learn'],
eat: function () {
console.log(this.name + '吃饭')
},
}
//使用
let lang = createObj(people)
缺点(同原型式继承)
- 不能传参
- 因为是浅复制,引用变量被共享了
寄生组合继承
之前组合继承 调用了两次 构造参数
Student.prototype = new People();
let lang = new Student('lang', 21, '男')
- 第一次的继承原型链的时候
Student.prototype
添加了name
和age
属性 - 第二次new 子类的实例的时候又添加
name
和age
属性
避免重复添加, 我们让 Student.prototype
访问 People.prototype
// 寄生式组合继承
function inheritPrototype(child, parent) {
// 新建一个原型
let prototype = Object.create(parent.prototype)
// 弥补缺陷添加 constructor 指向 child
prototype.constructor = child
// 子类的原形 指向 新建的原型
child.prototype = prototype
}
function People(name, age) {
this.name = name
this.age = age
this.dd = function () {
console.log(this.name + '打车')
}
}
People.prototype.eat = function () {
console.log(this.name + '吃饭')
}
function Student(name, age, sex) {
// 直接偷构造
People.call(this, name, age)
// 顺便给自己添加个属性
this.sex = sex
}
// 寄生式组合继承
inheritPrototype(Student, People)
// 添加子类的方法
Student.prototype.see = function () {
console.log(this.name + '能看见千里之外的景物')
}
let lang = new Student('lang', 21, '男')
lang.eat() //lang吃饭
lang.dd() //lang打车
console.log(lang.sex, lang.name, lang.age) //男 lang 21
lang.dd = function () {
console.log('更改过了')
}
lang.dd() //更改过了
let jie = new Student('jie', 21, '男')
jie.dd() //jie打车
jie.see() //jie能看见千里之外的景物
优点
- 父类方法可以复用
- 可以传递参数到父类
- 父类的引用属性不会被共享
- 只调用一次父类构造函数
extends 继承
ES6提供了
calss
extends
super
来实现继承
// ES6继承
class People {
constructor(name, age) {
this.name = name
this.age = age
}
dd() {
console.log(this.name + '打车')
}
}
People.prototype.eat = function () {
console.log(this.name + '吃饭')
}
class Student extends People {
constructor(name, age, sex) {
super(name, age)
this.sex = sex
}
}
// 添加子类的方法
Student.prototype.see = function () {
console.log(this.name + '能看见千里之外的景物')
}
let lang = new Student('lang', 21, '男')
lang.eat() //lang吃饭
lang.dd() //lang打车
console.log(lang.sex, lang.name, lang.age) //男 lang 21
lang.dd = function () {
console.log('更改过了')
}
lang.dd() //更改过了
let jie = new Student('jie', 21, '男')
jie.dd() //jie打车
jie.see() //jie能看见千里之外的景物
如何捕获JS程序中的异常?
通过
try...catch...
捕获
try {
//to do
}
catch (ex) {
console.log("error", ex.message); // error oops
}finally{
//to do
}
window.onerror
自动捕获第一,对跨域的js,如CDN的,不会有详细的报错信息 第二,对于压缩的js,还要配合sourceMap 反查到未压缩代码的行、列
window.onerror = function(message, source, linenom, colnom, error) { ... }
message
:错误信息(字符串)source
:发生错误的脚本URL(字符串)linenom
:发生错误的行号(数字)colno
m:发生错误的列号(数字)error
:Error对象(对象)
window.addEventListener('error')
window.addEventListener('error', function(event) { ... })
什么是JSON ?
json
是一种数据格式,本质是一-段字符串json
格式和JS
对象结构一致,对JS
语言更友好
获取当前页面url参数
传统方式,查找location.search
新API
URL SearchParams
传统方式
// 传统方式
function query(name) {
const search = location.search.substr(1) // 类似 array.slice(1)
const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i')
const res = search.match(reg)
if (res === null) {
return null
}
return res[2]
}
query('d')
URL SearchParams
function query(name) {
const search = location.search
const p = new URLSearchParams(search)
return p.get(name)
}
console.log(query('b'))
将url
参数解析为JS
对象
传统方式, 分析 srarch
function queyrToObj() {
const res = {}
const search = location.search.substr(1)//截取?之后的参数
search.split('&').forEach(paramStr => {
const arr = paramStr.split('=')
const key = arr[0]
const val = arr[1]
res[key] = val
})
return res
}
使用 URLSearchParams
function queryToObj() {
const res = {}
const pList = new URLSearchParams(location.search)
pList.forEach((val,key) => {
res[key] = val
})
return res
}
扁平化数组和手写数组flatern
考虑多层级
比如 有这么一个数组 [1, 2, [3, 4, [10, 20, [100, 200]]], 5]
输出:[1, 2, 3, 4, 10, 20, 100, 200, 5]
使用 concat
let arr = [1, 2, [3, 4, [10, 20, [100, 200]]], 5]
while(arr.some( item => Array.isArray(item))){
arr = [].concat(...arr)
}
console.log(arr)
使用
stringify
把数组变成字符串 然后正则去除 所有[
]
,根据,
分割为数组,然后把每一项转为number
let arr = [1, 2, [3, 4, [10, 20, [100, 200]]], 5]
let res = JSON.stringify(arr).replace('/\[|\]/','').split(',').map(item => Number(item))
console.log(res)
使用toString 和 上面差不多
let arr = [1, 2, [3, 4, [10, 20, [100, 200]]], 5]
let res = arr.toString().split(',').map(item => Number(item))
console.log(res)
使用ES6 中flat就行
let arr = [1, 2, [3, 4, [10, 20, [100, 200]]], 5]
console.log(arr.flat(Infinity))
自己手写
flatern
Array.prototype.myFlat = function() {
function flat(){
// 验证 arr 中,还有没有深层数组 [1, 2, [3, 4]]
const isDeep = arr.some(item => item instanceof Array)
if (!isDeep) {
return arr // 已经是 flatern [1, 2, 3, 4]
}
const res = Array.prototype.concat.apply([], arr)
return flat(res) // 递归
}
}
const res = [1, 2, [3, 4, [10, 20, [100, 200]]], 5].myFlat()
console.log(res)
数组去重
- 传统方式,遍历元素挨个比较、去重
- 使用
Set
- 考虑计算效率
传统方式
function unique(arr) {
const res = []
arr.forEach(item => {
if (res.indexOf(item) < 0) {
res.push(item)
}
})
return res
}
使用
Set
function unique(arr) {
const set = new Set(arr)
return [...set]
}
手写深拷贝
// 封装深拷贝
function deepCopy(newObj, obj) {
for (const key in obj) {
//把对象或者数组的每一项都获取出来
var item = obj[key];
// 判断该项是否为对象
if (item instanceof Object) {
newObj[key] = {};
deepCopy(newObj[key], item);
//判断该项是否为数组
} else if (item instanceof Array) {
newObj[key] = [];
deepCopy(newObj[key], item);
// 这就是普通的类型的对象
} else {
newObj[key] = item;
}
}
return newObj
}
实现一个不可改变的对象
-
Object.isExtensible()方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
-
Object.preventExtensions()方法让一个对象变的不可扩展,也就是永远不能再添加新的属性,并且返回原对象。
-
Object.isSealed()方法判断一个对象是否是密封的。
-
Object.seal()方法可以让一个对象密封,并返回被密封后的对象。
-
Object.isFrozen()方法判断一个对象是否被冻结。
-
Object.freeze()方法可以冻结一个对象。
1.不可扩展: 不可以增加新的属性,老的属性可以删除,也可以改值 2.密封: 不可以增加新的属性,老的属性不可以删除,但可以改值 3.冻结:不可以增加新的属性,老的属性不可以删除,不可以改值
介绍一下RAF requestAnimateFrame
- 要想动画流畅,更新频率要60帧/s , 即
16.67ms
更新- -次视图 setTimeout
要手动控制频率,而RAF浏览器会自动控制- 后台标签或隐藏
iframe
中,RAF
会暂停,而setTimeout
依然执行 - 这里使用了
jQuery
// 3s 把宽度从 100px 变为 640px ,即增加 540px
// 60帧/s ,3s 180 帧 ,每次变化 3px
const $div1 = $('#div1')
let curWidth = 100
const maxWidth = 640
// setTimeout
function animate() {
curWidth = curWidth + 3
$div1.css('width', curWidth)
if (curWidth < maxWidth) {
setTimeout(animate, 16.7) // 自己控制时间
}
}
animate()
// RAF
function animate() {
curWidth = curWidth + 3
$div1.css('width', curWidth)
if (curWidth < maxWidth) {
window.requestAnimationFrame(animate) // 时间不用自己控制
}
}
animate()
前端性能如何优化? 一般从哪几个方面考虑?
- 原则:多使用内存、缓存,减少计算、减少网路请求
- 方向:加载页面,页面渲染,页面操作流畅度