web前端基础面试题(纯干货)更新中……
HTML
-
如何理解
html语义化?-
html语义化标签更易读(针对人)
-
让搜索引擎更容易读懂(SEO)(针对机器)
-
-
块状元素和内联元素?
display:block/table;有div h1 h2 table ul ol p等display:inline/inline-block;有span img input button等
css部分
1. 请问div1的offsetWidth是多大?(盒模型宽度计算)
<style>
#div1{
width:100px;
padding:10px;
border:1px solid #ccc;
margin:10px;
}
</style>
</head>
<body>
<div id="div1">this is div1</div>
</body>
知识点: offsetWidth = (内容宽度+内边距+边框),无外边距
因此答案是122px
console.log(document.getElementById('div1').offsetWidth);//122px
补充问题:如果让offsetWidth等于100px,该如何做?
- 解决方法:添加样式代码:box-sizing:border-box
- 解释:指定宽度和高度(最小/最大属性)确定元素边框。
也就是说,对元素指定宽度和高度包括了 padding 和 border(把padding和border算到了100px以内) 。通过从已设定的宽度和高度分别减去边框和内边距才能得到内容的宽度和高度。
- 解释:指定宽度和高度(最小/最大属性)确定元素边框。
2. AAA和BBB之间的距离是多少?(margin纵向重叠问题)
知识点:相邻元素的margin-top和margin-bottom会重叠,空p标签被忽略
答案:15px
<style> p{ font-size: 16px; line-height: 1; margin-top: 10px; margin-bottom: 15px; } </style> </head> <body> <p>AAA</p> <p></p> <p></p> <p></p> <p>BBB</p> </body>
3.margin的top,left,right,bottom设置负值,有何效果?(margin负值问题)
- margin-top和margin-left为负值,元素向上,想左移动
- margin-right负值,右侧元素左移,自身不受影响
- margin-bottom负值,下方元素上移,自身不受影响
4.什么是BFC?如何应用?(BFC的理解与应用)
- 简介
- Block format context ,块级格式化上下文
- 一块独立渲染区域,内部元素的渲染不会影响边界以外的元素
bfc元素表现的特性就是一个独立的盒子,内部的子元素再怎么折腾都不会影响外部的元素和父元素,
如果触发bfc,子盒子的margin和浮动不会对父盒子产生影响,所以可以用来清除浮动问题。
-
形成BFC的条件
- float不是none
- position是absolute或者fixed
- overflow 不是visible
- display是flex inline-block等
-
BFC常见应用
- 清除浮动
- 利用BFC避免margin重叠。
<style>
div {
background: pink;
margin: 20px 0;
}
li{
overflow: hidden;
}
</style>
</head>
<body>
<ul>
<li>
<div>1</div>
</li>
<li>
<div>1</div>
</li>
<li>
<div>1</div>
</li>
<li>
<div>1</div>
</li>
</ul>
- 自适应两栏布局
3-5
5.如何实现圣杯布局和双飞翼布局?(float布局)
-
圣杯布局的目的
- 三栏布局,中间一栏最先加载和渲染(内容最重要)
-
两侧内容固定,中间内容随着宽度自适应
-
一般用于PC网页
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>圣杯布局</title> <style type="text/css"> body { min-width: 550px; } #header { text-align: center; background-color: #f1f1f1; } #container { padding-left: 200px; padding-right: 150px; } #container .column { float: left; } #center { background-color: #ccc; width: 100%; /* */ } #left { position: relative; background-color: yellow; width: 200px; margin-left: -100%; /* margin-left移动后只能覆盖上边浮动的块元素, 因为padding是container的留白,所以#left元素不会出现在上边. 因为是浮动所以left元素本来就应该是在上面的,但是被挤下来了 */ right: 200px; } #right { background-color: red; width: 150px; margin-right: -150px;/*margin-left移动后只能覆盖上边浮动的块元素,padding是container的留白,#left元素不会出现在上边*/ /* margin-right: -150x; 正常效果是右侧的元素向左移动150px,占据它原来的所有空间(没有右侧元素), 也就相当于它自身的宽度在外界看来已经是0了,又因为它本来就是在上边的(三个元素都是浮动),因为有宽度而被挤下来了,所以现在它没有宽度了,所以就上去了 */ /* 其他实现方式: position: relative; left: 150px; margin-left: -150px; */ } #footer { text-align: center; background-color: #f1f1f1; } /* 手写 clearfix */ .clearfix:after { content: ''; display: table; clear: both; } </style> </head> <body> <!--position的值为absolute、fixed的元素脱离文档流,static、relative没有脱离文档流--> <!--块级元素一个一排地从上向下堆放,行内元素从左往右地堆放,这就是一个标准的流程--> <div id="header">this is header</div> <div id="container" class="clearfix"> <div id="center" class="column">this is center</div> <div id="left" class="column">this is left</div> <div id="right" class="column">this is right</div> </div> <div id="footer">this is footer</div> </body> </html>
-
圣杯布局和双飞翼布局的技术总结
-
使用float布局
-
两侧使用margin负值,以便和中间内容横向重叠
-
防止中间内容被两侧覆盖,一个用padding一个用margin
-
圣杯布局是用外层盒子的padding进行留白,所以内层盒子要想出现在留白处除了使用margin-left(rigth)还要借助相对定位。
而双飞翼布局是利用中间盒子的margin进行留白,所以直接用margin-left(rigth)就可以出现在留白处了<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> .main{ width: 100%; } .center{ margin:0 150px 0 200px; background-color: red; } .f{ float: left; } .left{ width: 200px; background-color: pink; margin-left: -100%; } .right{ width: 150px; background-color: purple; margin-left: -150px; } </style> </head> <body> <div class="main f"> <div class="center">this is center</div> </div> <div class="left f">this is left</div> <div class="right f">this is right</div> </body> </html>
-
6.手写clearfix
原理:通过CSS伪元素在容器的内部元素之后添加一个看不见的空格“/20”或点“.” ,并且设置clear属性清除浮动
.clearfix:after {
content: ''; //和:before 及 :after 伪元素配合使用,来插入生成内容。
display: table;//设置成块状元素,或许display:block;也可以
clear: both;//在左右两侧均不允许浮动元素。
}
.clearfix{
zoom:1;/*兼容IE低版本*/
}
7.flex实现三个点的色子(flex布局)
- flex实现一个三点的色子
- 要求熟练掌握属性:
flex-directionjustify-contentaligin-itemsflex-wrapalign-self
8. absolute和relative分别依据什么定位
-
relative依据自身定位
-
absolute依据最近一层的定位元素定位
-
定位元素:
absolute relative fixedbody
先找
absolute relative fixed如果没有就直接参考body定位
-
9. 居中对齐有哪些实现方式?
- 水平居中
- inline元素:
text-align:center - block元素:
给宽度+margin:auto - absolute元素:
left:50%+margin-left负值(需要知道子元素的尺寸)
- inline元素:
- 垂直居中
- inline元素:
line-height的值等于height值 - absolute元素:
top:50% + margin-top负值(需要知道子元素的尺寸) - absolute元素:
transform(-50%,50%)(不需要知道尺寸) - absolute元素:
top,left,bottom,right=0+margin:auto(不需要知道尺寸)
- inline元素:
3-10
10.图文样式
line-height如何继承:
<style type="text/css">
body {
font-size: 20px;
line-height: 200%;
}
p {
background-color: #ccc;
font-size: 16px;
}
</style>
<body>
<p>
这是一行文字
</p>
</body>
line-height: 50px;line-height为确定数值,那么p标签的line-height直接继承50pxline-height: 1.5;line-height为比例数值,那么p标签的继承的line-height为16*1.5=24px(补充知识:font-size=line-height=16)line-height: 200%;line-height为百分比的形式,那么p标签的继承的line-height为40px(父元素的font-size)*200%=40px
11.响应式
rem:
-
px,绝对长度单位,最常用
-
em,相对长度单位,相对父元素,不常用
-
rem,相对长度单位,相对于根元素(html元素),常用于响应式布局
- 换算尺度由html设置,如:html的
font-size为100px,那么子元素的font-size的1rem就是100px,0.16rem就是16px - 除了
font-size以外任何使用长度单位的情况也都可以使用rem
- 换算尺度由html设置,如:html的
响应式布局的常用方案:
-
media-query,根据不同的屏幕宽度设置根元素属性值,例如:
font-size<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>响应式布局</title> <style type="text/css"> @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 { /*以iphone6/7/8 和 iphone x为标准*/ font-size: 100px; /* 这是标准,以它为基础可以定义86px和110px 100/(375/320)=86 100/(375/414)=110 */ } } @media only screen and (min-width: 414px) { /* iphone6p 或者更大的尺寸,以 iphone6p 的宽度(414px)比例设置 font-size */ html { font-size: 110px; } } body { font-size: 0.16rem; /* 因为上边的html设置的不同,在不同的手机上显示的具体数值不同 */ } #div1 { width: 1rem; /* 因为上边的html设置的不同,在不同的手机上显示的具体数值不同 */ background-color: #ccc; } /* 任何元素都可以使用响应式*/ </style> </head> <body> <div id="div1"> this is div </div> </body> </html> -
rem,基于根元素的相对单位
vw/vh
-
rem的弊端
- 弊端:阶梯性
-
网页视口尺寸
-
window.screen.height //屏幕高度
-
window.innerHeigth //网页视口高度 =100vh
-
document.body.clientHeigth //body高度
-
- vw/vh
- 优点:比rem方式更加细腻,避免了阶梯性
- 1vh是网页视口高度的1/100
- 1vw是网页视口宽度的1/100
- vmax取两者最大值;vmin取两者最小值
- 优点:比rem方式更加细腻,避免了阶梯性
12.css面试总结
- 如何理解HTML语义化?
- 让人更容易读懂(增加代码可读性)
- 让搜索引擎更容易读懂(SEO)
- 常见的块状元素和内联元素有哪些?
- display:block/table;有div h1 h2 table ul ol p等
- display:inline/inline-block;有span img input button等
- 盒模型宽度如何计算?
- offsetWidth = (内容宽度+内边距+边框),无外边距
- 让元素的width=offsetWidth:使用box-sizing:border-box;
- margin 纵向重叠问题
- 相邻元素的margin-top和margin-bottom会发生重叠
- 空白内容的p标签也会重叠
- 元素margin负值问题
- margin-top和margin-left负值,元素向上,向左移动
- margin-rigth负值,右侧元素左移,自身不受影响
- margin-bottom负值,下方元素上移,自身不受影响
- BFC理解和应用
- 一块独立渲染区域,内部元素的渲染不会影响边界以外的元素
- 形成BFC的条件:
- xxx见上文
- BFC的常见应用
- 清除浮动
- 圣杯布局和双飞翼布局的技术总结
- 使用float布局
- 两侧使用margin负值,以便和中间内容横向重叠
- 防止中间内容被两侧覆盖,一个用padding一个用margin
- 手写clearfix
- flex布局(画三个点的色子)
- absolute和relative的定位
- relative依据自身定位
- absolute依据最近一层的元素定位
- 居中对齐的实现方式
- 水平居中
- 垂直居中
- line-height继承
- rem是什么
- 响应式布局的常用方案
JS部分
13.变量类型和计算
题目:
- typeof能判断哪些类型
- 何时使用=何时使用
- 值类型和引用类型的区别
- 手写深拷贝
知识点:
-
变量类型
- 值类型vs引用类型
//值类型
let a = 100;
let b = a;
a = 200;
console.log(b);//100
//常见的值类型
let a;//undefined
const s = 'abc'
const n = 100;
const b = true;
const s = Symbol('s');
// 引用类型
let a = {age:20};
let b = a;
b.age = 21;
console.log(a.age)//21
// 常见的引用类型
const obj = {x:100};
const arr = ['a','b','c'];
const n = null//特殊引用类型,指针指向为空地址
//特殊引用类型,但不用于存储数据,所以没有"拷贝,复制函数"这一说
function fn(){}
4-1
接上文……
-
typeof 运算符
- 识别所有值类型(太简单不演示了,注意特别的Symbol)
const s = Symbol('s'); typeof s//'symbol'- 识别函数
- 判断是否是引用类型(不可再细分)
-
深拷贝
var obj1 = {
age:18,
arr:[0,1,2],
person:{
name:'yang',
sex:'nan'
}
};
var obj2= {};
function deepClone(obj1,obj2){
if (typeof obj1!=='object' || obj1 == null){
return obj1;
}else{
for(var k in obj1){
if (obj1.hasOwnProperty(k)){
if (typeof obj1[k] == 'object'){
//obj2[k] 赋值为数组或者对象,用于在下一轮循环中接收原对象中的数组或者对象
obj2[k] = obj1[k].constructor==Array?[]:{};
// obj2[k] = Object.prototype.toString.call(obj1[k]) == "Array" ? [] : {};
deepClone(obj1[k],obj2[k]);
} else{
obj2[k] = obj1[k];
}
}
}
}
}
deepClone(obj1,obj2);
console.log(obj2)
var obj1 = {//原始对象
age: 18,
arr: [1, 2, 3],
};
var obj2 = {};
function copy(obj1, obj2) {
for (var k in obj1) {
if (typeof obj1[k] == 'object') {
//obj2[k] 赋值为数组或者对象,用于在下一轮循环中接收原对象中的数组或者对象
obj2[k] = (obj1[k].constructor == Array) ? [] : {};
// obj2[k] = Object.prototype.toString.call(obj1[k]) == "Array" ? [] : {};
copy(obj1[k], obj2[k])
} else {
obj2[k] = obj1[k];
}
}
return obj2;
}
var obj2 = copy(obj1, obj2);
console.log(obj2);
obj1.age = 199;
obj1.arr[0] = 'zhangsan';
console.log(obj1, obj2)
//注意:深拷贝同时复制了基本类型和引用类型的属性,所以任何一个对象的引用类型属性或者基本类型属性改变都不会相互影响
//利用JSON深拷贝,原理还是递归
//var obj2 = JSON.parse(JSON.stringify(obj1));
/**
* 深拷贝
*/
const obj1 = {
age: 20,
name: 'xxx',
address: {
city: 'beijing'
},
arr: ['a', 'b', 'c']
}
const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city)
console.log(obj1.arr[0])
/**
* 深拷贝
* @param {Object} obj 要拷贝的对象
*/
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// obj 是 null ,或者不是对象和数组,直接返回
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用!!!
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
//浅拷贝对象
var obj1 = {
age: 18,
arr: [1, 2, 3]
};
var obj2 = {};
function copy(obj1) {
for (var k in obj1) {
obj2[k] = obj1[k];
}
return obj2;
}
var obj2 = copy(obj1);
console.log(obj2 == obj1);
obj1.age = 33;
obj1.arr[0] = 'ooo';
console.log(obj1, obj2)
//注意:浅拷贝只是复制了基本类型,所以其中一个对象的基本类型的属性改变是不会影响到另一个对象的,
// 但是两个对象的引用类型属性值发生改变还是会互相影响的
// 利用assign()浅拷贝
// var obj2 = {};
//Object.assign(目标对象, 原始对象)
// Object.assign(obj2, obj1);
// obj1.arr[0] = 'ooo';
// console.log(obj1, obj2)
-
特殊的值
undefined和nullnull指空值(曾经赋过值,但是目前没有值),特殊关键字,不是标识符,不能当做变量使用和赋值undefined指没有值(从未赋过值),是一个标识符,可以当做变量使用
-
特殊的数字
NaN(不是数字的数字)- 理解为"无效数值","失败数值"或者"坏数值",指出数字类型中的错误情况即:"数学运算没有成功,这是失败后返回的结果"
NaN是一个特殊值,它和自身不相等,但是NaN!=NaN//true
var a = 2/"foo"; a == NaN //false a === NaN //false window.isNaN(a) //true,有bug不可取 Number.isNaN(a) //true ES6工具函数,准确 -
假值(假值得布尔强制类型转换结果为false,但是假值得封装对象是真值)
undefinednullfalse+0,-0和NaN""
-
真值:假值列表以外的值都是真值
-
变量计算-类型转换
-
字符串拼接
- 当需要将非数字当做数字来使用,比如数学运算,遵循以下规则:
true转换为1false转换为0undefined转换为NaNnull转换为0
const a = 100 + 10 //110 const b = 100 + '10' //'10010' const c = true + '10' //'true10' const d = undefined + 100 //NaN const e = 100 + null //100 - 当需要将非数字当做数字来使用,比如数学运算,遵循以下规则:
-
==和===运算-
==是宽松相等:允许在相等比较中进行强制类型转换,而===不允许 -
==两边的值转换规则(两边的值分别转换成什么类型的规则)-
数字和字符串:(简单说:字符串转换成数字)
- 如果
Type(x)是数字,Type(y)是字符串,则返回x==ToNumber(y)的结果 - 如果
Type(x)是字符串,Type(y)是数字,则返回ToNumber(x)==y的结果
- 如果
-
其他类型和布尔类型:(简单说:布尔类型转成数字类型)
- 如果
Type(x)是布尔类型,则返回ToNumber(x)==y的结果 - 如果
Type(y)是布尔类型,则返回x==ToNumber(y)的结果
- 如果
-
null和undefined
- 如果
x为null,y为undefined,则结果为true - 如果
x为undefined,y为null,则结果为true
在
==中null和undefined相等(与其自身也相等),除此之外其他值都不和它们相等
也就是说在==中null和undefined是一回事,可以相互隐式强制类型转换。 - 如果
-
100=='100' //true 0 == '' //true 0 == false //true false == '' //true null == undefined //true NaN==NaN //false +0==-0 //true var a = '3.14'; var b = a - 0; b//3.14 -
-
if语句和逻辑运算
-
||(或)和&&(与)或许叫["选择运算符"]更准确,因为它返回的是两个操作数中的一个- 比较规则:
- 先进行条件判断第一个值是true还是false,如果第一个值不是布尔值,那么就先将它进行强制类型转换,然后执行条件判断。
- 对于
||来说,如果条件判断结果是true就返回第一个值,如果是false就返回第二个值 &&正好相反,如果条件判断结果是true就返回第二个值,如果是false就返回第一个值
- 比较规则:
引述ES5规范11.11节:
&&和||运算符的返回值不一定是布尔类型,而是两个操作数其中一个的值:
见书《你不知道的JS》中册74页 -
14.原型和原型链
题目:
-
如何判断一个变量是不是数组?
var arr = [1,2,3]; function isArray(arr){ //instance of 回答的问题是:在arr整条【prototype】链中是否有Array.prototype指向的对象? // return arr instanceof Array?'数组':'' return arr.constructor == Array?'数组':'' }; console.log(isArray(arr)) -
手写一个简易的jQuery,考虑插件和扩展性
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> </style> </head> <p>一段文字 1</p> <p>一段文字 2</p> <p>一段文字 3</p> <body> <script> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> </style> </head> <p>一段文字 1</p> <p>一段文字 2</p> <p>一段文字 3</p> <body> <script> class jQuery { constructor(selector) { const result = document.querySelectorAll(selector); const length = result.length; for (let i = 0; i < length; i++) { this[i] = result[i] } this.length = length; this.selector = selector } get(index) { return this[index] } each(fn) { for (let i = 0; i < this.length; i++) { let elem = this[i]; fn(elem) } } on(type, fn) { return this.each(elem => { elem.addEventListener(type, fn, false) }) } // 扩展很多 DOM API } // 插件 jQuery.prototype.dialog = function (info) { alert(info) } // “造轮子” class myJQuery extends jQuery { constructor(selector) { super(selector) } // 扩展自己的方法 addClass(className) { } style(data) { } } const $p = new jQuery('p'); console.log($p.get(1)); $p.each((elem) => console.log(elem.nodeName)); $p.on('click', (elem) => alert(elem.target.innerHTML)) -
class的原型本质,怎么理解
- 实例的隐式原型
__proto__=== (指向)对应的类的显示原型(prototype) - 子类的隐式原型
__proto__指向父类的显示原型prototype
- 实例的隐式原型
知识点:
-
class和继承
-
class
-
constructor
-
属性
-
方法
// 类 class Student { constructor(name, number) { this.name = name this.number = number // this.gender = 'male' } sayHi() { console.log( `姓名 ${this.name} ,学号 ${this.number}` ) // console.log( // '姓名 ' + this.name + ' ,学号 ' + this.number // ) } // study() { // } } // 通过类 new 对象/实例 const xialuo = new Student('夏洛', 100) console.log(xialuo.name) console.log(xialuo.number) xialuo.sayHi() const madongmei = new Student('马冬梅', 101) console.log(madongmei.name) console.log(madongmei.number) madongmei.sayHi() console.log(typeof Student); //function
-
-
继承
-
extends
-
super
-
扩展或重写方法
// 父类 class People { constructor(name) { this.name = name } eat() { console.log(`${this.name} eat something`) } } // 子类 class Student extends People { constructor(name, number) { super(name) this.number = number } sayHi() { console.log(`姓名 ${this.name} 学号 ${this.number}`) } } // 子类 class Teacher extends People { constructor(name, major) { super(name) this.major = major } teach() { console.log(`${this.name} 教授 ${this.major}`) } } // 实例 const xialuo = new Student('夏洛', 100) console.log(xialuo.name) console.log(xialuo.number) xialuo.sayHi() xialuo.eat() // 实例 const wanglaoshi = new Teacher('王老师', '语文') console.log(wanglaoshi.name) console.log(wanglaoshi.major) wanglaoshi.teach() wanglaoshi.eat()
-
-
5-2
- 类型判断
instanceof - instanceof回答的问题是new函数所创建的对象的整条【prototype】链中是否有构造它的函数的prototype属性指向的对象
- new函数所创建的对象的整条【prototype】链中
- 是否有一个对象
- 这个对象是构造它的函数的prototype属性指向的对象
- 原型和原型链
- 原型关系
- 每个
class都有显式原型prototype - 每个实例都有隐式原型
_proto_ - 实例的
_proto_指向对应class的prototype
- 每个
- 基于原型的执行规则
- 获取属性xialuo.name或执行方法xialuo.sayhi()时
- 先在自身属性和方法中寻找
- 如果找不到则自动去_proto_中去查找
- 原型关系
15.作用域,自由变量
-
作用域
-
全局作用域
-
函数作用域
- 属于这个函数的全部变量都可以在整个函数的范围内部使用以及复用(事实上在嵌套的作用域中也可以使用)。
-
块级作用域(es6新增)
- let关键字可以将变量绑定到所在的任意作用域中(通常就是{}内部),也就是,let为其声明的变量隐式的劫持了所在的块作用域(简单说块作用域就是{})
//es6块级作用域 if(true){ let x = 100 } cosole.log(x) //会报错 -
-
自由变量
- 一个变量在当前作用域没有定义,但是被使用了
- 向上级作用域,一层一层一次寻找,直至找到为止
- 如果到全局作用域都没有找到,则报错xx is undefined
-
闭包
-
作用域应用的特殊情况,有两种表现:
- 函数作为参数被传递
- 函数作为返回值被返回
//函数作为返回值 function create() { let a = 100; return function () { console.log(a); } } let fn = create(); let a = 200; fn()//100 // 函数作为参数 function print() { let a = 200; fn(); } let a = 100; function fn() { console.log(a); } print(fn)//100 // 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找 // 不是在执行的地方!!!
-
6-2
16.this
《你不知道的JS》中this总结
-
默认绑定
function(){ console.log(this.a); } var a = 2; foo();//2 this指向全局对象window -
隐式绑定
function foo(){ console.log(this.a); } var obj = { a:2, foo:foo }; obj.foo();//2 //当foo()被调用时,它的前边加上了对obj的引用,当函数引用上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。 //this指向调用当前方法/函数的对象 -
显示绑定(call,apply),硬绑定(bind)
call(obj,参数1,参数2) apply(obj,[参数1,参数2]),可以用数组传递参数 bind(obj,参数1,参数2……),返回一个硬编码函数 function foo(){ console.log(this.a); } var obj={ a:2 } foo.call(obj)//2 //我们可以在调用foo时强制把它的this绑定到obj上 -
new 绑定
- 创建/构造一个全新的对象
- 这个新对象会被执行【Prototype】链接
- 这个新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么new 表达式中的函数会自动返回这个新对象
function foo(a){ this.a = a; } var bar = new foo(2); console.log(bar.a);//2
-
作为普通函数
-
使用
call apply bind-
手写bind函数:
// 模拟 bind Function.prototype.bind1 = function () { // 将参数拆解为数组 const args = Array.prototype.slice.call(arguments); // 获取 this(数组第一项) const t = args.shift(); // fn1.bind(...) 中的 fn1 const self = this; // 返回一个函数 return function () { return self.apply(t, args) } }; function fn1(a, b, c) { console.log('this', this); console.log(a, b, c); return 'this is fn1' } const fn2 = fn1.bind1({x: 100}, 10, 20, 30); const res = fn2(); console.log(res); -
原生bind函数
function fn(a, b, c) { console.log('this', this); console.log(a, b, c); return 'this is fn1' } const fn2 = fn.bind({x: 100}, 10, 20, 30); const res = fn2(); console.log(res);
-
-
作为对象方法被调用
-
在class方法中被调用
- 指向当前实例本身
-
箭头函数
- 永远找它上级作用域的this来确定
-
this取值是在函数执行时确认的,不是在函数定义的时候确定的
function fn1(){ console.log(this); } fn1()//window fn1.call({x:100}) //{x:100} const fn2 = fn1.bind({x:200}) fn2() //{x:200}
//setTimeout中this指向问题也可以用bind()函数解决,其原理是bind()函数内部是用call或者apply实现的this显示绑定(改变this指向)
wait(){
setTimeout(function(){
console.log(this);
}.bind(this))
}
16.实际可发中闭包的应用
-
隐藏数据
-
做一个简单的cache工具
// 闭包隐藏数据,只提供 API function createCache() { const data = {}; // 闭包中的数据,被隐藏,不被外界访问 return { set: function (key, val) { data[key] = val }, get: function (key) { return data[key] } } } const c = createCache(); c.set('a', 100); console.log( c.get('a') ); -
for循环中作用域和闭包问题i是全局变量
let i, a; for (i = 0; i < 10; i++) { a = document.createElement('a'); a.innerHTML = i + '<br>'; a.addEventListener('click', function (e) { e.preventDefault(); alert(i) }); document.body.appendChild(a) } //输出结果0 1 2 …… 9 的a标签,点击每个都是弹出10,原因是for循环是在点击事件发生前就已经执行完,此时i的值是10,又因为i定义的是全局变量,所以每次点击都是10i是局部变量(块级作用域)
let a; for (let i = 0; i < 10; i++) { a = document.createElement('a'); a.innerHTML = i + '<br>'; a.addEventListener('click', function (e) { e.preventDefault(); alert(i) }); document.body.appendChild(a) } //输出结果0 1 2 …… 9 的a标签,点击每个标签都会弹出对应的数值(0-9),原因是i被定义在局部作用域(块作用域),每次循环都形成一个新的块,那么每次都会给i赋予新的值(let 会把变量绑定到块作用域({})中)-
关于for循环setTimeout考察的循环次数细节问题
for (var i = 0; i < 3; i++) { setTimeout(()=>{ console.log(i); //输出3次3 当i=2时已经循环3次,当i==2循环结束后进行下一轮循环时,i++,此时i==3,i==3不符合条 件,所以终止循环,但是i还是会被加1等于3的,只不过等于3后下一轮不符合条件,所以不会打印 }) } for (var i = 0; i <= 3; i++) { setTimeout(()=>{ console.log(i); //输出4次4 当i=3是(此时已经循环3次)符合条件继续循环(一共循环4次),循环结束后i++,i+1=4, 但是i==4十不符合条件,不会打印,就终止循环 }) }
17.异步和单线程
题目
-
同步和异步的区别是什么?
-
手写Promise加载一张图片
-
前端使用异步的场景有哪些?
-
场景题:1,2,3,4,5以什么顺序打印出来?
//setTimeout笔试题 console.log(1) setTimeout(function(){ console.log(2) },1000) console.log(3) setTimeout(function(){ console.log(4) },0); console.log(5); //1,3,5,4,2
知识点
-
单线程和异步
-
JS是单线程语言,只能同时做一件事儿(产生异步的原因)
-
浏览器和node.js已经支持JS启动进程,如web worker
-
JS 和DOM渲染共用一个线程,因为JS可修改DOM结构
-
遇到等待(网络请求,定时任务)不能卡住,所以需要异步
-
异步回调callback函数形式
// 异步会执行(callback 回调函数) (setTiemout里面的) console.log(100) setTimeout(() => { console.log(200) }, 1000)//不会卡在这里,会先向下执行,执行完毕后再后执行回调函数 console.log(300) console.log(400)// 同步 console.log(100) alert(200)//卡在这里,确认在继续执行 console.log(300) -
基于JS是单线程语言的本质,异步不会阻塞代码
-
同步会阻塞代码的执行
-
7-1
-
异步应用场景(等待的情况)
-
网络请求,如ajax和图片加载 这时cpu是空闲的,所以要有异步,这样cpu才能饱和工作
//ajax console.log('start'); $.get('./data1.json',function(data1){ console.log(data1); }) console.log('end'); /* 1.打印start. 2.执行网络请求(耗费时间),跳过继续向下执行 3.打印end 4.打印完end,网络请求什么时候完毕,什么时候就执行回调函数,返回请求数据 *///图片加载 console.log('start'); let img = document.createElement('img'); img.onload = function(){//onload 事件会在页面或图像加载完成后立即发生 console.log('loaded'); } //加载图片 img.src = '/xxx.png'; console.log('end'); //这里的onload是另一种形式的回调函数,程序执行到img.src进行加载图片,会花费一些时间,此时程序不会等待,而是会继续向下执行,打印end,什么时候图片加载完成,什么时候回调用onload函数,打印’onload‘ -
定时任务,如setTimeout,这时cpu是空闲的,所以要有异步,这样cpu才能饱和工作
//setTimeout setTimeout(() => { console.log(200) }, 1000)//不会卡在这里,会先向下执行,执行完毕后再掉执行回调函数 console.log(300) //setInterval console.log(100) setInterval(function(){ console.log(200) },1000) console.log(300)
-
-
Callback hell 和 Promise
Callback hell:假设业务开发中有4个接口,每个接口都依赖于前一个接口的返回,即request2依赖request1,request3依赖request2,新手可能会写出如下代码
-
问题解答:
-
异步同步的区别是什么?
- 基于JS是单线程语言
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
-
手写用Promise加载一张图片
const url1 = 'https://img1.sycdn.imooc.com/5968a7860001fed401000100-140-140.jpg'; const url2 = 'https://img3.sycdn.imooc.com/szimg/5eddd7aa09cfdb1c12000676-360-202.png' function loadImg(src) { //pending return new Promise((resolve,reject)=>{ const img = document.createElement('img'); img.onload = ()=>{//onload也是一种回调形式 resolve(img);//resolved } img.onerror=()=>{ const err = new Error(`图片加载失败${src}`); reject(err);//rejected } img.src = src; }) } //1.加载一张图片 /* loadImg(url).then(img=>{ console.log(img.width); return img; }).then(img=>{ console.log(img.height); }).catch(ex=>console.error(ex)) */ //2.加载两张图片 loadImg(url1).then(img1=>{ console.log(img1.width);//普通对象 return img1; }).then(img1=>{ console.log(img1.height); return loadImg(url2);//promise实例 }).then(img2=>{ console.log(img2.width); return img2; }).then(img2=>{ console.log(img2.height) }).catch(ex=>console.error(ex))
-
-
前端使用异步的场景有哪些
- 网络请求,如ajax,图片加载
- 定时任务,如setTimeout
JS基础总结
-
内容回顾
- 变量的类型和计算
- 原型和原型链
- 作用域和闭包
- 异步和单线程
-
回顾题目
- typeof能判断哪些类型
- 何时使用=何时使用
- 值类型和引用类型的区别
- 手写深拷贝
-
回顾知识点
- 值类型vs引用类型,堆栈模型,深拷贝
- typeof 运算符作用
- 类型转化
-
原型和原型链的题目
-
如何准确判断一个变量是不是一个数组?
- intanceof
-
手写一个jQuery,考虑插件和扩展性
-
class的原型本质,怎么理解
-
-
原型和原型链--知识点
- class和继承,结合手写jQuery的实例来理解
- instanceof
- 原型和原型链:图示和执行规则
-
闭包和作用域-题目
- this的不同应用长江,如何取值
- 手写bind函数
- 实际开发中闭包的应用场景,举例说明
- 坑:创建10个a标签,点击……
-
作用域和闭包--知识点
- 作用域和自由变量
- 闭包:两种常见形式&自由变量查找规则
- this
-
异步和单线程-题目
- 同步和异步的区别是什么?
- 手写promise加载一张图片
- 前端使用异步的场景有哪些
- setTimeout定时器的场景题
-
异步单线程-知识点
- 单线程和异步,异步和同步的区别
- 前端异步的应用场景:网络请求&定时任务
- Promise解决callback hell
8-1
18 异步的面试题
问答题
- 请描述event loop (事件循环/事件轮询)的机制,可画图
- 什么是宏任务和微任务,两者有什么区别?
- Promise有哪三种状态?如何变化?
- 场景题-promise then 和catch 的链接

// 第一题
Promise.resolve().then(() => {
//返回resolved状态的promise后会执行.then的回调,.then的回调又会返回resolved状态的promise,resolved状态的promise不会执行catch,它会执行.then的回调。整个最后返回resolved状态的promise
console.log(1)
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
}) //resolved
// 1 3
// 第二题
Promise.resolve().then(() => {
//.then里报错,那么.then返回的是rejected状态的promise,rejected的promise触发.catch回调,因为.catch没有报错,所以返回的是resolved状态的promise,resolved状态的promise会触发.then。整个最后返回的还是resolved状态的promise
console.log(1)
throw new Error('erro1')
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
}) //resolved
// 1 2 3
// 第三题
Promise.resolve().then(() => {//返回rejected状态的promise 触发 catch回调
console.log(1)
throw new Error('erro1')
}).catch(() => { //返回resolved状态的promise 触发 then回调
console.log(2)
}).catch(() => { // 注意这里是 catch
console.log(3)
})
// 1 2
Promise.reject().then(()=>{
console.log(1);
throw new Error('error1');
}).then(()=>{
console.log(2);
}).catch(()=>{
console.log(3);
})
//3
Promise.reject().catch(()=>{
console.log(1);
throw new Error('error1');
}).then(()=>{
console.log(2);
}).catch(()=>{
console.log(3);
})
//1,3
Promise.reject().catch(()=>{
console.log(1);
// throw new Error('error1');
}).then(()=>{
console.log(2);
}).catch(()=>{
console.log(3);
})
//1,2
Promise.reject().catch(()=>{
console.log(1);
}).then(()=>{
console.log(2);
throw new Error('error1');
}).catch(()=>{
console.log(3);
})
//1,2,3
Promise.resolve().catch(() => {
console.log(1);
}).then(() => {
console.log(2);
throw new Error('error1');
}).catch(() => {
throw new Error('error2');
console.log(3);
})
//2
-
场景提 - async/await 语法
(async function(){ console.log('start'); const a = await 100; console.log('a',a); const b = await Promise.resolve(200); console.log('b',b); const c = await Promise.reject(300); //reject状态的Promise,不会执行await要用try...catch捕捉异常错误,此题报错显示没有捕获的错误,所以后边的代码不会执行 console.log("c",c); console.log('end'); })() //start //a 100 //b 200 //Uncaught (in promise) 300async function fn(){ return 100; } (async function(){ const a = fn(); console.log(a); //调用async修饰的函数返回的是promise对象 且这里的值是100 promise{<fulfilled>:100} const b = await fn(); console.log(b);//await相当于then 所以直接返回数值100 })() -
场景提 - promise 和setTimeout的顺序
console.log(100); setTimeout(()=>{ console.log(200); }) Promise.resolve().then(()=>{ console.log(300); }) console.log(400); -
场景题 - 外加 async/await 的顺序问题
//网上经典面试题 async function async1 () { console.log('async1 start') //2 //await后面都是回调内容--微任务 await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行 console.log('async1 end') //6 上面有 await ,下面就变成了“异步”,类似 cakkback 的功能(微任务) } async function async2 () { console.log('async2')//3 } console.log('script start')//1 setTimeout(function () { // 异步,宏任务 console.log('setTimeout')//8 }, 0) async1() //初始化promise时,传入的函数会立刻被执行 new Promise (function (resolve) { // 返回 Promise 之后,即同步执行完成,then 是异步代码 console.log('promise1') //4 Promise 的函数体会立刻执行 resolve() }).then (function () { // 异步,微任务 console.log('promise2')//7 }) console.log('script end')//5 // 同步代码执行完之后,屡一下现有的异步未执行的,按照顺序 // 1. async1 函数中 await 后面的内容 —— 微任务 // 2. setTimeout —— 宏任务 // 3. then —— 微任务 //除了宏任务和微任务其他都是同步的
event loop
- JS是单线程运行的
- 异步要基于回调来实现
- event loop 就是异步回调的实现原理
JS 如何执行?
-
从前到后,一行一行执行
-
如果某一行报错,则停止下面代码的执行
-
先把同步代码执行完,再执行异步
-
示例
-
总结event loop过程
第一步:把
console.log('Hi')这行代码推入调用栈调用栈执行代码,执行完之后打印出Hi
![image-20210405120619279]()
第二步:这行代码执行完毕,清空调用栈
第三步:执行到setTimeout()函数(浏览器定义(WebAPIs)的),把异步函数cb1放到定时器里面,定时是5s,5s之后把cb1放到Call Queue里面,至此setTimeout函数(三行代码)结束,console里不会打印任何东西

第四步: 执行最后一行代码
console.log("Bye"),和第一行一样,把它推送到调用栈中执行打印Bye执行完毕之后,调用栈清空。此时后边已经没有要在被推送到callstack中执行的代码了,也就是同步代码已经执行完毕了,这时就会启动EventLoop(浏览器内核自动启动),他会像永动机或者死循环一样不断的执行循环,它会在callback queue(异步回调函数)里去找函数,如果有的话它会把函数放到callStack里执行,cb1有函数体,把
console.log('cb1')放到调用栈里执行, 最后清空调用栈-
Call Stack:真正触发执行某行代码
-
Web APIs:对于浏览器的运行环境和一些API的定义,setTimeout(setInterval,dom操作)不是SE里定义的,而是浏览器里面定义的API.处理定时或者异步的api的
-
Event Loop:当Call Stack 空了的时候(同步代码执行完毕,尝试dom渲染后)开始触发,它就像永动机一样或者想死循环一样不停地在callback Queue中找有没有可以执行的函数,如果有的话就把它移动到Call Stack中执行,
-
Callback Queue:回调函数的队列
-
-
同步代码,一行一行放在call stack执行
-
遇到异步,会先’‘记录’’下,等待时机(定时,网络请求等)
-
时机到了,就移动到Callback Queue
-
如果Call Stack 为空,尝试渲染dom之后,(即同步代码执行完)Event Loop开始工作
-
轮询查找Callback Queue中的回调函数,如有则移动到CallStack执行,再尝试渲染dom
-
然后继续轮询查找(像永动机一样)
-
Callstack,尝试dom渲染,触发下一次eventloop 轮询查找是循环往复的
DOM事件和event loop
-
都是回调函数,但是触发的方式和时机不同,一个是设置固定时间,一个是用户点击后触发
-
JS是单线程的
-
异步(setTimeout(定时),ajax(网络请求返回时)等)使用回调,基于event loop
-
DOM(事件触发时,如点击,鼠标滚动,键盘事件) 事件也是基于回调,基于event loop,但dom事件不是异步
执行细节:当代码运行到
$('#btn1').click()(这些代码浏览器立刻执行的),同样会把回到函数function(e){log("")}它放到webAPIs里,等到同步代码执行完毕,尝试渲染dom(Event Loop开始轮询),用户触发点击事件时,会把回调函数(function(e){log("")})放到callback Queue中,然后Event Loop再把回调函数放到调用栈callStack中 执行
Promise有哪三种状态
-
三种状态
- pending resolved rejected
- pending=>resolved 或 pending=>rejected
- 变化不可逆
-
状态的表现和变化
-
pending状态promise,不会触发then和catch
-
resolved状态promise,会触发后续的then回调函数
-
rejected 状态promise,会触发后续的catch回调函数
const p1 = new Promise((resolve,reject)=>{ }) console.log('p1',p1);//pending const p2 = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve() }) }) console.log('p2',p2);//pending 一开始打印时 setTimeout(()=>console.log('p2-setTimeout',p2))//resolved const p3 = new Promise((resolve,reject)=>{ setTimeout(()=>{ reject() }) }) console.log('p3',p3);//pending 一开始打印时 setTimeout(()=>console.log('p3-setTimeout',p3))//rejectedconst p1 = Promise.resolve(100)//返回resolved状态的promise console.log('p1',p1); p1.then(data=>{ console.log('data',data); }).catch(err=>{ console.error('err',err); }) const p2 = Promise.reject('err')//rejected // console.log('p2',p2); p2.then(data=>{ console.log('data2',data); }).catch(err=>{ console.error('err2',err); })
-
8-6
-
then和catch对状态的影响
-
then和catch改变状态- then没有报错返回resolved状态的promise,接着会触发后边的then回调,里面有报错则返回rejected状态的promise,
会触发后边的catch回调 - catch没有报错返回resolved状态的promise,接着会触发后边的then回调,里面有报错则返回rejected状态的promise,
会触发后边的catch回调
- then没有报错返回resolved状态的promise,接着会触发后边的then回调,里面有报错则返回rejected状态的promise,
-
总结
- 三种状态,状态的表现和变化
then和catch对状态的影响(重要)then和catch的链式调用(常考)
async/await
- 异步回调
callback hell Promise then catch链式调用,但是也是基于回调函数Async/await是同步语法,彻底消灭回调函数
async/await和Promise的关系
-
async/await是消灭异步回调的终极武器 -
但是和
Promise并不互斥 -
反而两者相辅相成
-
执行
async函数,反回的是Promise对象 -
await相当于Promise的then -
try...catch可捕获异常,代替了Promise的catchasync function fn1() { // return 100;//封装成Promise对象返回 相当于return Promise.resolve(100) return Promise.resolve(200); }; // const res1 = fn1(); //执行async函数,返回的是一个Promise对象 // console.log('res1', res1); // res1.then(data => { // console.log('data', data); // }) // //await 后边跟promise // (async function(){ // const p1 = Promise.resolve(300); // const data = await p1;//如果await后边跟的是promise,那么await 相当于 Promise 的then // console.log('data',data); // })(); // //await 后边跟数值 // (async function(){ // const data1 = await 400;//如果await后边跟的是非Promise的值,那么会把它封装成Promise对象,相当于 await Promise.resolve(400) // console.log('data1',data1); // })(); // //await 后边跟async函数 // (async function(){ // const data2 = await fn1(); // console.log('data2',data2); // })(); // (async function(){ // const p4 = Promise.reject('err1');//rejected // try { // const res = await p4; // console.log(res); // } catch (ex) { // console.error(ex);//try...catch 相当于promise的catch // } // })() (async function(){ const p4 = Promise.reject('err1');//rejected const res = await p4; //await 相当于 then ,但是这里的p4是rejected状态的promise所以不会执行到await,这种情况就要用到上边的try...catch console.log('res',res); })()
异步的本质
-
async/await是消灭异步回调的终极武器 -
JS还是单线程,还得是有异步,还得是基于event loop
-
async/await 只是语法糖,但是这颗糖真香
//面试题 async function async1(){ console.log('async1 start'); await async2(); //undefined //await的后面,都可以看做是callback里的内容,即异步,所以暂时不会打印。函数体执行完,接着会执行后边的script end,此时同步代码迎执行完,最后打印saync1 end //类似,event loop ,setTimeout(cb1) //setTimeout(function(){console.log('async1 end')})或者Promise.resolve().then(()=>{console.log('async1 end')}) console.log('async1 end'); await async3(); console.log('async1 end 2'); } async function async2(){ console.log('async2'); } async function async3(){ console.log('async3'); } console.log('script start'); async1(); console.log('script end'); //1,script start 2.async1 start 3.async2 4.script end 5.async1 end 6,async3 7async1 end 2
8-11
for..of
for...in(以及forEach for)是常规的同步遍历for...of常用于异步的遍历
function muti(num){
return new Promise(resolve=>{
setTimeout(()=>{
resolve(num*num)
},1000)
})
}
const nums = [1,2,3];
//同步循环
//如果使用foreach遍历处理异步代码不会等待异步代码的执行,一次输出所有异步结果
// nums.forEach(async(i)=>{
// const res = await muti(i);
// console.log(res);
// })
//异步循环
//果使用for-of可以使异步按先后顺序执行
//运用async/await方法,当异步函数里执行resolve时,await接收到成功的指示,然后进行下一步
(async function(){
for(let i of nums){
const res = await muti(i);
console.log(res);
}
})();
async/await总结
- async/await 只是语法糖,但是这颗糖真香
- async/await和Promise的关系
- for...of的使用
宏任务macroTask和微任务microTask
- 什么是宏任务,什么是微任务
- 宏任务:
setTimeout,setInterval,Ajax,DOM事件- dom渲染后触发,如setTimeout
- 微任务:
Promise async/await- dom渲染前触发,如promise
- 微任务执行时机比宏任务要早(记住)
- 宏任务:
- event loop 和 DOM渲染
- 宏任务和微任务的区别
event loop 和DOM渲染
- 再次回顾一遍eventloop的过程
- JS是单线程的,而且和DOM渲染共用一个线程
- JS执行的时候,得留一些时机提供DOM渲染
回顾eventloop过程(增加dom渲染时机)
当同步代码执行完毕时,callstack会处于空闲状态(空的),这是浏览器会尝试dom渲染(如果代码中有操作),然后再第一次启动eventloop,如果callback queue中有回调函数,那么会把它放到callstack中去执行,执行完毕再尝试dom渲染,再启动eventloop.所以,每次eventloop轮询或者callstack执行完毕后都会尝试dom渲染
-
每次Call Stack清空(即每次轮询结束),即同步任务完成
-
都是dom重新渲染的机会,dom结构如有改变(修改dom的代码)则重新渲染
-
然后再去触发下一次eventloop
//证实callstack结束 之后会渲染dom const $p1 = $('<p>一段文字</p>'); const $p2 = $('<p>一段文字</p>'); const $p3 = $('<p>一段文字</p>'); $('#container').append($p1).append($p2).append($p3); console.log('length',$('#container').children().length); alert('本次 call stack 结束,dom 结构已更新,但是尚未触发渲染'); //alert 会阻断js执行,也会阻断dom渲染,便于查看效果//证实微任务是dom渲染前触发的,而宏任务是在dom渲染后触发的 const $p1 = $('<p>一段文字</p>'); const $p2 = $('<p>一段文字</p>'); const $p3 = $('<p>一段文字</p>'); $('#container').append($p1).append($p2).append($p3); //微任务:dom渲染前触发 Promise.resolve().then(()=>{ console.log('length1',$('#container').children().length);//3 alert('Promise then');//dom渲染了吗? --no }) //宏任务:dom渲染后触发 setTimeout(() => { console.log('length2',$('#container').children().length);//3 alert('setTimeout');//dom渲染了吗? --yes });从eventloop角度解释,为什么微任务执行的时机更早(根本区别)
-
微任务是ES6语法规定的
-
宏任务是由浏览器规定的
-
Micro tast queue:微任务队列
-
callstack中执行到微任务代码时,会等待时机,然后直接把它放到微任务队列(Micro tast queue)
-
callstack清空之后->执行微任务->dom渲染->触发eventloop->把回调函数放到callstack执行宏任务
//微任务宏任务面试真题 console.log(100) setTimeout(() => { console.log(200) }) Promise.resolve().then(() => { console.log(300) }) console.log(400) // 100 400 300 200
8-16
//网上经典面试题
async function async1 () {
console.log('async1 start') //2
//await后面都是回调内容--微任务
await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行
console.log('async1 end') //6 上面有 await ,下面就变成了“异步”,类似 cakkback 的功能(微任务)
}
async function async2 () {
console.log('async2')//3
}
console.log('script start')//1
setTimeout(function () { // 异步,宏任务
console.log('setTimeout')//8
}, 0)
async1()
//初始化promise时,传入的函数会立刻被执行
new Promise (function (resolve) { // 返回 Promise 之后,即同步执行完成,then 是异步代码
console.log('promise1') //4 Promise 的函数体会立刻执行
resolve()
}).then (function () { // 异步,微任务
console.log('promise2')//7
})
console.log('script end')//5
// 同步代码执行完之后,屡一下现有的异步未执行的,按照顺序
// 1. async1 函数中 await 后面的内容 —— 微任务
// 2. setTimeout —— 宏任务
// 3. then —— 微任务
//除了宏任务和微任务其他都是同步的
从JS基础知识到JS Web API
- JS基础知识,规定语法(ECMA 262标准)
- JS Web API,网页操作的API(w3c标准)
- 前者是后者的基础,两者结合才能真正实际应用
JS基础知识
- 变量类型计算
- 原型和原型链
- 作用域和闭包
JS Web API
- DOM
- BOM
- 事件绑定
- ajax
- 存储
前言
- vue和React框架应用广泛,封装了DOM操作
- 但是DOM操作一直都是前段工程师的基础,必备知识
- 只会vue而不会DOM操作的前端程序员,不会长久
题目
- DOM是那种数据结构
- dom树
- DOM操作常用的API
- attr 和 property 的区别?
- attr对dom结构的节点的属性修改,修改html属性,会改变html结构
- property对js变量修改,修改对象属性值,不会体现到html中
- property 和 attribute 都可能引起dom重新渲染,优选property
- 一次性插入多个DOM节点,考虑性能
- dom节点缓存
- fragment
知识点
-
dom本质
- 从html文件解析出来的树
-
dom节点操作
-
获取dom节点
-
attribute
- 对dom结构的节点的属性修改,修改html属性,会改变html结构
-
property
- 对js变量修改,修改对象属性,不会体现到html中
-
property 和 attribute 都可能引起dom重新渲染,优选property
-
9-3
-
dom结构操作
-
新增/插入节点
-
获取子元素列表,获取父元素列表
-
利用nodeType检查文本元素还是普通dom节点
-
如果节点是一个元素节点,nodeType 属性返回 1。
-
如果节点是属性节点, nodeType 属性返回 2。
-
如果节点是一个文本节点,nodeType 属性返回 3。
-
-
-
删除子元素
-
dom性能
-
dom操作非常‘昂贵’,避免频繁的dom操作
-
对dom查询做缓存
-
for循环中如果条件是需要对
dom的length进行计算,那就要把dom的length计算写在for循环外边,定义一个新的变量保存,然后在for循环中使用这个自己定义的变量,可以避免因多次dom查询带来的性能消耗
-
-
将频繁操作改为一次性操作
- 如要创建多个新的
dom挂到原有的dom节点上,可以先通过document.createDocumentFragment()创建一个文档片段,然后再把新创建的节点通过for循环和appendChild添加到文档片段上,最后将文档片段通过appendChild插入到最初指定的dom节点上
- 如要创建多个新的
-
BOM操作
题目
- 如何识别浏览器的类型
- 分析拆解url各个部分
知识点:
-
navigator
- userAgent: 属性是一个只读的字符串,声明了浏览器用于 HTTP 请求的用户代理头的值。
-
screen
-
location
-
history
事件
题目
-
编写一个通用的事件监听函数
-
描述事件冒泡的流程
- 基于dom树形结构
- 事件会顺着触发元素往上冒泡
- 应用场景:代理
-
无限下拉的图片列表,如何监听每个图片的点击?
-
事件代理
-
用e.target获取触发元素
-
用matches来判断是否触发元素
<button id="btn1">一个按钮</button> <div id="div3"> <a href="#">a1</a><br> <a href="#">a2</a><br> <a href="#">a3</a><br> <a href="#">a4</a><br> <button>加载更多...</button> </div> function bindEvent(elem, type, selector, fn) { console.log(selector); console.log(fn); if (fn == null) { fn = selector; selector = null } elem.addEventListener(type, event => { const target = event.target; console.log(target); if (selector) { // 代理绑定 if (target.matches(selector)) { fn.call(target, event) } } else { // 普通绑定 fn.call(target, event) } }) } // 普通绑定 const btn1 = document.getElementById('btn1') bindEvent(btn1, 'click', function (event) { // console.log(event.target) // 获取触发的元素 event.preventDefault() // 阻止默认行为 alert(this.innerHTML) }) // 代理绑定 const div3 = document.getElementById('div3') bindEvent(div3, 'click', 'a', function (event) { event.preventDefault() alert(this.innerHTML) })
-
知识点
-
事件绑定
//通用事件绑定函数 function bindEvent(elem,type,fn){ elem.addEventListener(type,fn); } const btn1 = document.getElementById('btn1'); bindEvent(btn1,'click',event=>{ event.preventDefault() alert('clicked') }) -
事件冒泡
<style> #div1 { width: 400px; height: 400px; background-color: red; } #div2 { width: 100px; height: 100px; background-color: pink; } </style> </head> <body> <div id="div1"> div1 <div id="div2">div2</div> </div> <a href="http://www.baidu.com" id="baidu">百度</a> <script> baidu.onclick = function (e) { //window.event 兼容ie let ev = window.event || e; if (ev.preventDefault){ ev.preventDefault(); } else{ //兼容ie ev.cancelValue = false; } }; div1.onclick = function () { alert(1); }; div2.onclick = function (e) { alert(2); let ev = window.event || e; if (ev.stopPropagation) { ev.stopPropagation(); } else { //兼容ie ev.cancelable; } //stopPropagation w3c标准浏览器 //ie 非w3c标准 } </script>
11-1
- 事件代理
- “事件代理(事件委托)”即是把原本需要绑定在子元素的响应事件(click、keydown......)委托给父元素,让父元素担当事件监听的职务,多用于子元素个数不确定或者量很大的情况。通过事件冒泡触发父元素绑定的事件,再根据
e.target.nodeName或者e.target.matches获取触发事件的具体子元素,对子元素进行进一步处理 - 代码简洁
- 减少浏览器内存占用
- 但是,不要滥用
- “事件代理(事件委托)”即是把原本需要绑定在子元素的响应事件(click、keydown......)委托给父元素,让父元素担当事件监听的职务,多用于子元素个数不确定或者量很大的情况。通过事件冒泡触发父元素绑定的事件,再根据
<button id='btn'>添加按钮</button>
<ul id='uls'>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var lis = document.getElementsByTagName("li"),
btn = document.getElementById("btn"),
uls = document.getElementById("uls");
btn.onclick = function(){
var len = lis.length;
var li = document.createElement("li");
li.innerHTML = len;
uls.appendChild(li);
}
uls.onclick = function(e){
var e = window.event || e;
var tar = e.target || e.srcElement;
if(tar.nodeName=='LI'){
tar.style.background='red';
}
}
//本身是给li添加事件,现在委托在ul身上
</script>
</body>
</html>
ajax
题目:
-
手写一个简易的ajax
//XMLHttpRequest 对象用于在后台与服务器交换数据。 //get请求 const xhr = new XMLHttpRequest(); xhr.open('GET','./data/test.json',true);//true 异步请求 xhr.onreadystatechange = function () { if (xhr.readyState===4){ if(xhr.status === 200){ // console.log(JSON.parse(xhr.responseText)) alert(xhr.responseText) }else if(xhr.status === 404){ console.log('404 not found') } } } xhr.send(null) //post请求 const xhr = new XMLHttpRequest(); xhr.open('POST','./login',true); xhr.onreadystatechange = function () { if (xhr.readyState===4){ if(xhr.status === 200){ // console.log(JSON.parse(xhr.responseText)) alert(xhr.responseText) } } } const postData = { userName:'zhangsan', password:'xxx' } xhr.send(JSON.stringify(postData));//必须发送字符串类型//promise形式ajax请求 function ajax(url) { const p = new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() xhr.open('GET', url, true) xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { resolve( JSON.parse(xhr.responseText) ) } else if (xhr.status === 404 || xhr.status === 500) { reject(new Error('404 not found')) } } } xhr.send(null) }) return p } const url = './data/test.json' ajax(url) .then(res => console.log(res)) .catch(err => console.error(err)) -
跨域的常用实现方式
- JSONP
- CORS
知识点:
-
XMLHttpRequest
-
状态码
-
xhr.readyState
- 0 (未初始化)还没有调用send()方法
- 1(载入)已调用send()方法,正在发送请求
- 2(载入完成)send()方法执行完成,已经收到全部响应内容
- 3(交互)正在解析响应内容
- 4(完成)响应内容解析完成,可以在客户端调用(只需要这一种)
-
xhr.status(**)
- 2xx -表示成功处理请求,如200
- 3xx 需要走重定向,浏览器直接跳转,如301(永久重定向),302(临时重定向),304(资源为改变,使用缓存资源)
- 4xx 客户端请求错误,如404(找不到地址),403(客户端没有权限)
- 5xx 服务器端错误
-
-
跨域:同源策略,跨域解决方案
- 什么是跨域(同源策略)
- ajax请求时,浏览器要求当前网页和server必须同源(安全)
- 同源:协议(http,https),域名(xxx.com),端口(80port),三者必须一致
- 前端:
http://a.com:8080/;服务端:https://b.com/api/xxx(端口默认是80)不能跨域 - 所有跨域,都必须经过server端允许和配合
- 未经server端允许就实现跨域,说明浏览器有漏洞,危险信号
- JSONP
- CORS(服务端支持)
- 什么是跨域(同源策略)
-
加载图片css,js可无视同源策略(浏览器不限制)
<img src="跨域的图片地址"/><img/>可以用于统计打点,可以使用第三方统计服务<link/><script>可以使用CDN,CDN一般都是外域<script>可以实现JSONP
<link href="跨域的css地址"/><script src="跨域的js地址"></script>
解答:
12-2
JSONP
-
访问http://imooc.com/,服务端一定返回一个html文件吗?
-
服务器可以任意动态拼接数据返回,只要符合html格式要求
-
同理于
<script src="https://imooc.com/getData.js"> -
<script>可以绕过跨域限制 -
服务器可以任意动态拼接数据返回
-
所以,
<script>就可以获得跨域的数据,只要服务端愿意返回
<body>
<p>一段文字 1</p>
<script>
window.abc = function (data) {
console.log(data)
}
</script>
<!--html端口和访问的jsonp.js端口不同,实现了跨域-->
<script src="http://localhost:8002/data/jsonp.js?username=xxx&callback=abc"></script>
<!--
<script src="http://localhost:8002/data/jsonp.js?username=xxx"></script>
服务器端可以动态拼接前端传过去的数据返回,例如username
<script src="http://localhost:8002/data/jsonp.js?username=xxx&callback=abc"></script>
可以修改全局函数名
-->
</body>
//jsonp文件
abc(
{ name: 'xxxgfhdfgh' }
)
存储cookie
题目
- 描述cookie localStorage sessionStorage区别
知识点
-
cookie
- 本身用于浏览器和server通讯
- 被‘借用’到本地存储来
- 可用document.cookie=' '来修改
- 存储大小,最大存储4kb
- http请求时要发送到服务端,增加请求数量
- 只能用document.cookie = ' ' 来修改,太过简陋
-
localStorage和sessionStorage
- HTML5专门为存储设计,最大可存5M
- API简单易用setItem,getItem
- 不会随着http请求被发送出去
- localStorage数据会永久存储,除非代码或手动删除
- sessionStorage数据值存在于当前会话,浏览器关闭则清空
- 一般用localStorage会更多一些
13-2
http
- 前端工程师开发界面
- 需要调用后端的接口,提交/获取数据---http协议
- 要求事先掌握好ajax
题目
- http常见的状态码有哪些
- 状态码分类
- 1xx服务器收到请求
- 2xx请求成功,如200
- 3xx重定向,如302
- 4xx客户端错误,如404(请求的地址服务端找不到)
- 5xx服务端错误,如500
- 常见状态码
- 200成功
- 301永久重定向(配合location(新的地址),浏览器自动处理):浏览器自己记住新地址,下次直接访问,使用情况:网站域名到期了或者想换域名了,老的域名就可以返回301状态码然后返回一个location 等于新的域名。以后浏览器不会再访问老的域名
- 302临时重定向(配合location,浏览器自动处理):浏览器每次都会访问老地址,例如百度搜索引擎中访问其他网站,都是第一时间访问百度地址,然后再跳转到目标地址
- 304 资源未被修改:服务端告诉你,之前请求的资源在本地还有效,不需要重新请求了
- 404 资源未找到:请求的地址服务端找不到
- 403 没有权限
- 500 服务器错误
- 504 网关超时:能访问到服务器,但是服务器内部在做处理,比如做跳转或者连接其他服务器(比如数据库)的访问时造成超时
- 关于协议和规范
- 就是一个约定
- 要求大家都跟着执行
- 不要违反规范,例如IE浏览器
- 状态码分类
- http常见的header有哪些
- 什么是RestfulAPI
- REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移
- API 设计规范,用于 Web 数据接口的设计
- 描述下http的缓存机制(重要)
http methods
- 传统的methods
- get获取服务器的数据
- post向服务器提交数据
- 简单的网页功能,就是这两个操作
- 现在的methods
- get获取数据
- post新建数据
- patch/put更新数据
- delete删除数据
- Restful API
- 一种新的API(前端后端交互的api)设计方法(早已推广使用)
- 传统API设计理念:把每个url当做一个功能 (*)
- Restful API设计理念 :把每个url当做一个唯一的资源(*)。如何设计成一个资源?
- 尽量不用url参数
- 传统的API设计:/api/list?pageIndex=2(很像一个函数+一个参数的样子)
- Restful API 设计: /api/list/2(没有参数,就是一个资源标识,list的第二页)
- 用method表示操作类型(传统的API设计,当做功能设计)做一个博客列表
- post请求 /api/create-blog
- post请求 : /api/update-blog?id=100
- get请求 : /api/get-blog?id=100
- 用method表示操作类型(Restful API设计)
- post 请求: /api/blog
- patch 请求: /api/blog/100
- get 请求: /api/blog/100
- 尽量不用url参数
http headers
- 常见的Request Headers
- Accept 浏览器可以接受的数据格式
- Accept-Encoding 浏览器可接受的压缩算法,如gzip
- Accept-Language浏览器可以接受的语言,如zh-CN
- Connections:keep-alive 一次TCP连接重复使用(客户端和服务端建立连接之后就会重复的使用这个链接,不会断开,把资源一次性请求完成)
- cookie:每次同域请求资源是浏览器自己携带cookie
- Host 请求的域名是什么
- User-Agent:浏览器信息(简称UA),例如:分析一个网站多少人用了Chrome浏览器,多少人用了Safari,IE浏览器,和不同的操作系统信息,都是通过服务端收到的UA来分析
- Content-type 发送数据的格式(post请求中),如applications/json
- 常见的Response Headers
- Content-type 服务端返回数据的格式,如applications/json
- Content-length 返回数据的大小,多少字节
- Content-Encoding 返回数据的压缩算法,如gzip
- Set-Cookie 服务端修改cookie
自定义header
* 用于简单的前端验证
缓存相关的headers
- Cache-Control Expires
- Last-Modified If-Modified-Since
- Etag If-None-Mathc
14-5
http 缓存
- 什么是缓存
- 把没必要重新从服务端获取的资源暂存一份,节省请求服务器浪费的时间
- 为什么需要缓存
- 通过缓存可以减少网络请求的数量和体积,让整个页面加载和渲染的速度更快一些
- 哪些资源可以被缓存? ---- 静态资源(js css img)
强制缓存
-
Cache-Control
-
max-age:设置最大缓存过期时间
-
no-cache:不用本地缓存,正常的向服务端请求,服务端进行处理(服务端可能有缓存措施),比如html资源,不想做缓存
-
no-store:不用本地缓存,也不用服务端的一些缓存措施(服务端“协商缓存”),更彻底
-
private:只能允许最终用户作为缓存
-
public:允许中间用户或者代理作为缓存
-
在Response Headers 中( 由服务端决定哪个资源可以缓存,服务端添加)
-
控制强制缓存的逻辑
-
例如:Cache-Control:max-age = 31536000(单位是秒):让某个资源(js文件)在客户端缓存一年的时间
-
-
-
Expires
- 也是在Response Headers中
- 也是控制缓存过期
- 已经被Cache-Control代替
协商缓存(对比缓存)
- 服务端缓存策略(判断一个请求资源是否可以使用本地缓存)
- 服务器判断客户端资源,是否和服务端资源一样,一样就使用本地缓存
- 如果一致则返回304,否则返回200和最新的资源
-
资源标识
-
在Response Headers中,有两种
-
Last-Modified资源的最后修改时间
-
Etag资源的唯一标识(一个字符串,类似人类的指纹)
-
优先使用Etag
-
Last-Modified只能精确到秒级
-
如果资源被重复生成,而内容不变,则Etag更精确
-
三种刷新操作
- 正常操作:地址栏输入url,跳转链接,前进后退等
- 强制缓存失效,协商缓存有效
- 手动刷新:F5,点击刷新按钮,右击菜单刷新
- 强制缓存失效,协商缓存有效
- 强制刷新:ctrl+F5
- 强制缓存失效,协商缓存失效
http面试题 - 总结
14-10
开发环境
- git
- 调试工具
- 抓包
- webpack babel
- Linux 常用命令
git
- 常用的代码版本管理工具
- 大型项目需要多人协作开发,必须熟用git
- 如果不知道或者之前不用git,不会通过面试
- git服务端常见的有github,coding.net等
- 大公司会搭建自己的内网git服务
- 常用命令
- git add
- Git checkout xxx
- git commit -m 'xxx'
- git push origin master
- git pull origin master
- git branch
- git checkout -b xxx /git checkout xxx
- git merge xxx
15-6
抓包
- 移动端h5页,查看网络请求,需要用工具抓包
- Windows一般用fiddler
- Mac OS 一般用charles
- 手机和电脑连同一个局域网
- 将手机代理到电脑上
- 手机浏览网页,即可抓包
- 查看网络请求
- 网址代理
- https
webpack和babel
- ES6模块化,浏览器暂不支持
- ES6语法,浏览器并不完全支持
- 压缩代码,整合代码,以让网页加载更快
15-11
linux命令:
-
ls
-
ls-a 平铺形式查看
-
ll 列表形式查看
-
clear
-
rm -rf 删除文件夹内容(-rf强制删除所有内容)
-
cd
-
mv 修改文件名
-
mv 移动文件
-
cp 拷贝文件
-
touch 创建文件
-
vi:新建文件(vim编辑器)
- i或a 开始输入
- ESC 退出输入模式
- ESC -> :w 保存
- ESC -> :q 退出
- ESC -> :q! 强制退出(不保存)
- 查看文件 vi 文件名
-
cat 查看文件
-
head 查看文件(前几行)
-
tail 打印最末尾几行
-
grep 'babel' package.json 在.json文件中查找关键字babel
-
vimtutor 学习vim命令
运行环境
- 运行环境即浏览器(server 端有node.js)
- 下载网页代码,渲染出页面,期间会执行若干js
- 要保证代码在浏览器中:稳定且高效
- 网页加载过程
- 性能优化
- 安全
网页加载过程
题目
-
从输入url到渲染出页面的整个过程
-
下载资源:各个资源类型,下载过程
-
渲染页面:结合html,css javascript图片等
-
-
window.onload和DOMContentLoaded的区别
- window.onload资源全部加载完成才能执行,包括图片
- DOMContentLoaded DOM渲染完成即可,图片可能尚未下载
知识点
-
加载资源的形式
- html 代码
- 媒体文件,如图片,视频等
- Javascript css
-
加载资源的过程
-
DNS(Domain Name Syste)解析:域名->IP地址 (把域名转解析成ip地址)
- 用域名不直接作为ip地址的原因:
- 域名更好记
- ip地址在不同区域内不一样(做不同区域的均衡或者代理),访问域名的时候域名解析的服务会根据地域解析不同的域名,提升访问速度
- 使用域名就必须使用域名解析服务,手机或电脑访问一个域名的时候,真正访问的还是ip地址,域名只是方便使用和记忆的符号
-
浏览器根据ip地址向服务器发起http请求
-
服务器处理http请求,并返回给浏览器
-
-
渲染页面的过程
-
根据html代码生成DOM Tree
- html 文本代码通过浏览器解析成树结构
-
根据css生成CSSOM(css对象模型)
- css 文本代码通过浏览器解析成css结构化对象
-
DOM Tree 和cssom整合形成Render Tree(渲染树)
-
根据Render Tree渲染页面
-
遇到
<script>则暂停渲染,优先加载并执行JS代码,完成再继续(js 和 渲染公用一个线程,因为js可能会改变dom结构) -
直至把Render Tree 渲染完成

为什么link要放在head里?
为什么script标签要放在body后?
因为不需要等待图片视频所以DOMContentLoaded更快一些
-
16-4
性能优化
- 是一个综合性问题,没有标准答案,但是要求尽量全面
- 默写细节问题可能会单独提问:手写防抖,节流
- 只关注核心点,针对面试
性能优化原则
- 多使用内存,缓存或其他方法
- 减少cpu计算量,减少网络加载耗时
- (适用于所有编程的性能优化---空间换时间)
从何入手
-
加载更快
- 减少资源体积:压缩代码
- 减少访问次数:合并代码(减少访问次数),SSR服务器端渲染,缓存
- 使用更快的网络:CDN
-
让渲染更快
-
CSS放在head,js放在body最下面
-
尽早开始执行js,用DOMContentLoaded触发
-
懒加载(图片懒加载,上滑加载更多)
-
对DOM查询进行缓存
-
频繁dom操作,合并到一起插入dom结构
-
节流throttle防抖debounce
-
防抖场景
-
监听一个输入框的文字变化后触发change事件
-
直接用keyup事件,则会触发change事件
-
防抖:用户输入结束或暂停时,才会触发change事件
<input type="text" id="input1"> const input1 = document.getElementById('input1') let timer = null input1.addEventListener('keyup', function () { if (timer) { clearTimeout(timer) } timer = setTimeout(() => { // 模拟触发 change 事件 console.log(input1.value); // 清空定时器 timer = null }, 500) }) // 防抖 function debounce(fn, delay = 500) { // timer 是闭包中的 let timer = null; return function () { if (timer) { clearTimeout(timer) } timer = setTimeout(() => { fn.apply(this, arguments); timer = null }, delay) } } input1.addEventListener('keyup', debounce(function (e) { console.log(e.target); console.log(input1.value) }, 600));
-
-
节流
-
拖拽一个元素时,要随时拿到该元素被拖拽的位置
-
直接用drag事件,则会频繁触发,很容易导致卡顿
-
节流:无论拖拽速度多快,都会每隔100ms触发一次
const div1 = document.getElementById('div1') // let timer = null // div1.addEventListener('drag', function (e) { // if (timer) { // return // } // timer = setTimeout(() => { // console.log(e.offsetX, e.offsetY) // timer = null // }, 100) // }) // 节流 function throttle(fn, delay = 100) { let timer = null; return function () { if (timer) { return } timer = setTimeout(() => { fn.apply(this, arguments) timer = null }, delay) } } div1.addEventListener('drag', throttle(function (e) { console.log(e.offsetX, e.offsetY) }));
-
-
-
SSR(server side render)
- 服务器端渲染:将网页和数据一起加载,一起渲染
- 非SSR(前后端分离):先加载网页,在加载数据,在渲染数据
- 早先的JSP,ASP,PHP,现在的vue React SSR
-
静态资源加hash后缀,根据文件内容计算hash
文件内容不变,则hash不变,则url不变、
url和文件不变,则会自动触发http缓存机制,返回304

先加载一个预览图,在加载实际图


安全
- xss跨站请求攻击
- 一个博客网站,我发表一篇博客,其中嵌入
<script>脚本(document.cookie) - 脚本内容:获取cookie,发送到我的服务器(服务器配合跨域)
- 发布这篇博客,有人查看它,我轻松获取访问者cookie
- 预防:
- 替换特殊字符,如
<变成<>变成> <script>变为<script>,直接显示,而不会作为脚本执行- 前后端都替换
- 替换特殊字符,如
- 一个博客网站,我发表一篇博客,其中嵌入
- xsrf跨站请求伪造
- 你正在购物,看中了某个商品,商品id是100
- 付费接口是xxx.com/pay?id=100,但没有任何验证
- 我是攻击者,看中了一个商品,id是200
- 我向你发送一封电子邮件,邮件标题很吸引人
- 但邮件正文隐藏着
<img src=xxx.com/pay?id=200/> - 你查看邮件,就帮我购买了id是200的商品
- 预防:
- 使用post接口
- 增加验证,例如密码,短信验证码,指纹等
16-12
-
var和let const 的区别
- var是es5语法,let const 是es6语法;var有变量提升
- var和let是变量,可修改;const是常量,不可修改
- let const有块作用域,var没有
-
split()和join()区别
- split(separator) 方法用于把一个字符串分割成字符串数组。
- separator可选。指定要使用的分隔符。如果省略该参数,则使用逗号作为分隔符
- join() 方法用于把数组中的所有元素放入一个字符串。
- split(separator) 方法用于把一个字符串分割成字符串数组。
-
数组的pop push unshift shift 分别是是什么
-
功能是什么
-
返回值是什么
-
是否会对原数组造成影响
const arr = [10, 20, 30, 40] // pop 方法用于删除并返回数组的最后一个元素。 const popRes = arr.pop() console.log(popRes, arr); // shift 方法用于删除并返回数组的第一个元素。 const shiftRes = arr.shift() console.log(shiftRes, arr) // push const pushRes = arr.push(50) // 返回 原数组length console.log(pushRes, arr) // unshift const unshiftRes = arr.unshift(5) // 返回 原数组length console.log(unshiftRes, arr)
-
-
手写深度比较,模拟lodash isEqual
// 判断是否是对象或数组 function isObject(obj) { return typeof obj === 'object' && obj !== null } // 全相等(深度) function isEqual(obj1, obj2) { if (!isObject(obj1) || !isObject(obj2)) { // 值类型(注意,参与 equal 的一般不会是函数) return obj1 === obj2 } if (obj1 === obj2) { return true } // 两个都是对象或数组,而且不相等 // 1. 先取出 obj1 和 obj2 的 keys ,比较个数 const obj1Keys = Object.keys(obj1); const obj2Keys = Object.keys(obj2); if (obj1Keys.length !== obj2Keys.length) { return false } // 2. 以 obj1 为基准,和 obj2 一次递归比较 for (let key in obj1) { // 比较当前 key 的 val —— 递归!!! const res = isEqual(obj1[key], obj2[key]); if (!res) { return false } } // 3. 全相等 return true } // 测试 const obj1 = { a: 100, b: { x: 100, y: 200 } } const obj2 = { a: 100, b: { x: 100, y: 200 } } // console.log( obj1 === obj2 ) console.log( isEqual(obj1, obj2) ) const arr1 = [1, 2, 3] const arr2 = [1, 2, 3, 4] -
数组的api,有哪些纯函数?
-
不改变原数组(没有副作用)
-
返回一个新数组
-
纯函数
- concat()
- map()
- filter()
- slice()
-
非纯函数
-
push pop shift unshift -
forEach-
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。没有返回值undefined
注意: forEach() 对于空数组是不会执行回调函数的。
-
-
some- some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。
- some() 方法会依次执行数组的每个元素:
- 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
- 如果没有满足条件的元素,则返回false。
注意: some() 不会对空数组进行检测。
注意: some() 不会改变原始数组。
-
everyevery() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。 every() 方法使用指定函数检测数组中的所有元素: 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。 如果所有元素都满足条件,则返回 true。 注意: every() 不会对空数组进行检测。 注意: every() 不会改变原始数组 -
reducereduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。 reduce() 可以作为一个高阶函数,用于函数的 compose。 注意: reduce() 对于空数组是不会执行回调函数的。 返回计算结果
-
-
-
-
数组slice和splice的区别
-
功能的区别(slice-切片,splice-剪接)
-
参数和返回值
-
是否是纯函数
//slice是纯函数,返回新数组,不改变原数组 const arr = [10, 20, 30, 40, 50] const arr1 = arr.slice()//不传参数直接返回原函数内容 const arr2 = arr.slice(1, 4);//20,30,40 const arr3 = arr.slice(2);//30,40,50 从2截取到末尾 const arr4 = arr.slice(-3);//30,40,50 倒数第3位开始截取到末尾 // splice 非纯函数,修改原数组,返回被删除的元素数组 const spliceRes = arr.splice(1, 2, 'a', 'b', 'c'); //arr: [10, "a", "b", "c", 40, 50], spliceRes:20,30 const spliceRes1 = arr.splice(1, 2) //arr:[10,40,50] spliceRes1:20,30 const spliceRes2 = arr.splice(1, 0, 'a', 'b', 'c') //arr:[10, "a", "b", "c", 20, 30, 40, 50],spliceRes2:[]
-
-
[10,20,30].map(parseInt)返回的结果是什么?//parseInt(数值,进制); 每一位数值不能大于等于进制数 const res = [10, 20, 30].map(parseInt) console.log(res) // 拆解 [10, 20, 30].map((num, index) => { return parseInt(num, index); }); //10,NaN,NaN -
ajax请求get和post的区别?
- get请求一般用于查询操作,post一般用于用户提交操作
- get参数拼接在url上,post放在请求体内(数据体积更大)
- 安全性:post易于防止CSRF
-
函数call和apply的区别
fn.call(this,p1,p2,p3); fn.apply(this,arguments); -
事件代理(委托)是什么
-
闭包是什么,有什么特性?有什么负面影响
- 回顾作用域和自由变量
- 回顾闭包应用场景:作为参数被传入,作为返回值被返回
- 回顾:自由变量的查找,要在函数定义的地方(而非函数执行的地方)
- 负面影响
- 变量会常驻内存,得不到释放,闭包不要乱用
-
如何阻止事件冒泡和默认行为?
-
查找,添加,删除,移动DOM节点的方法?
-
如何减少DOM操作?
- 缓存DOM查询结果
- 多次DOM操作,合并到一次插入
18-6
-
解释jsonp原理,为何它不是真正的ajax?
-
浏览器的同源策略(服务端没有同源策略)和跨域
-
哪些html标签能绕过跨域?
-
jsonp原理
-
-
Document load 和ready的区别
-
== 和 === 的不同
-
函数声明和函数表达式的区别?
- 函数声明function fn(){...};
- 函数表达式const fn = function (){...};
- 函数声明会在代码执行前预加载,而函数表达式不会
-
new Object()和Object.create()的区别
- {}等同于new Object(),原型Object.prototype
- Object.create(null)没有原型
- Object.create({...})可以指定原型
-
关于this的场景题
-
关于作用域和自由变量的场景提-1
-
判断字符串以字母开头,后面字母数字下划线,长度6-30
-
关于作用域和自由变量的场景提-2
let a = 100; function test() { alert(a); a = 10; alert(a); } test(); alert(a); -
手写字符串trim方法,保证浏览器兼容性
-
如何获取多个数字中的最大值
function max() { const nums = Array.prototype.slice.call(arguments); let max = 0; nums.forEach(n=>{ if (n>max){ max = n; } }); return max; } -
如何使用js实现继承?
- class继承
- prototype继承
-
如何捕获JS程序中的异常
try { //todo }catch (ex) { console.error(ex);//手动捕获catch }finally { //todo }
-
什么是json?
-
json是一种数据格式,本质是一段字符串
-
json格式和js对象结构一致,对js语言更友好
-
window.JSON是一个全局对象:JSON.stringify JSON.parse
-
-
获取当前页面url参数
-
传统方式,查找location.search
-
新API,URLSearchParams
// // 传统方式 // function query(name) { // const search = location.search.substr(1) // 类似 array.slice(1) // // search: 'a=10&b=20&c=30' // const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i') // const res = search.match(reg) // if (res === null) { // return null // } // return res[2] // } // query('d') // URLSearchParams function query(name) { const search = location.search//?后边的所有内容 const p = new URLSearchParams(search) return p.get(name) } console.log( query('b') )
-
-
将url参数解析为js对象

-
手写数组flatern,考虑多层级
function flat(arr) { // 验证 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 = flat( [1, 2, [3, 4, [10, 20, [100, 200]]], 5] ) 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] } const res = unique([30, 10, 20, 30, 40, 10]) console.log(res)
-
-
手写深拷贝
-
介绍一下RAF requestAnimateFrame
-
要想动画流畅,更新频率要60帧/s,即16.67ms更新一次视图
-
setTimeout 要手动控制频率,而RAF浏览器会自动控制
-
后台标签或隐藏iframe中,RAF会暂停,而setTimeout依然执行
// 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()
-
-
前端性能如何优化?一般从哪几方面考虑?
- 原则:多使用内存,缓存,减少计算,减少网络请求
- 方向:加载页面,页面渲染,页面操作流畅度





浙公网安备 33010602011771号