前言
又快到了到金三四银找工作的黄金时间,通过这些时间做面试题整理了一些经典必问的面试题,相信会对大家有帮助!
如何知道(任何的)一个网站使用了多少种HTML标签?
1,获取所有的DOM节点
document.querySelectorAll('*')
此时得到的是一个NodeList集合,我们需要将其转化成数组,然后对其筛选
2,转化为数组
[...document.querySelectorAll('*')]
一个拓展运算符就搞定
3,获取数组每个元素的标签名
[...document.querySelectorAll('*')}.map(ele => ele.tagName)
使用一个map方法,将我们需要的结果映射到一个新数组
4,去重
new Set([...document.querySelectorAll('*').map(ele=>ele.tagName)).size
我们使用ES6中的set对象,把数组作为构造函数的参数,就实现了去重,在使用Set对象的size方法就可以得到有多少种HTML元素了
这段代码运行结果
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
第一步,输出script start
虽然有两个函数声明,有async关键字,但是没有调用,就不看,直接打印同步代码console.log(‘script start’)
第二步,输出async1 start
因为执行async1这个函数的时候,async表达式定义的函数也是立即执行
第三步,输出async2
因为async2是async定义的函数,输出async2并返回promise对象
第四步,输出promise1
因为执行到new promise,promise是立即执行,就先打印promise1
第五步,输出script end
因为上一步先打印了promise1,然后执行到resolve的时候,然后跳出promise继续向下执行,输出script end
第六步,输出promise2
因为前面的nue promise放进resolve回调,所以resolve被放到调用栈执行
第七步,输出async1 end
因为await定义的这个promise已经执行完,并且返回结果,所以继续执行async1函数后的任务,就是console.log(‘async1 end’)
第八步,输出setTimerout
setTimeout被放在最后被调用
怎么让里面的div垂直水平居中
列举6种
<div class="parent">
<div class="child">
</div>
</div>
<style>
.parent{
width: 550px;
height: 550px;
border: 1px solid #FF7F24;
/* 方法一:定位 */
/* position: relative; */
/* 方法二:margin: auto */
/* position: relative; */
/* 方法三:利用display:table-cell */
/* display:table-cell 加上 vertical-align:middle和text-align:center 使高度不同的元素都水平垂直居中,其中display:inline-block使这两个div在同一行显示 */
/* display: table-cell;
vertical-align: middle;
text-align: center; */
/* 方法四:利用display:flex */
/* display: flex;
justify-content: center;
align-items: center; */
/* 方法五:计算父盒子与子盒子的空间距离(这跟方法一是一个道理) */
/* 方法六:利用transfrom */
/* position: relative */
/* 方法七:利用calc计算 */
/* position: relative; */
}
.child{
width: 150px;
height: 150px;
border: 1px solid #FF3030;
/* 方法一:定位 */
/* position: absolute;
top: 50%;
left: 50%;
margin-top: -75px;
margin-left: -75px; */
/* 方法二:margin: auto */
/* top: 0,left: 0,right: 0,bottom: 0就像四个方向有相同的力在拉这个盒子,然后margin:auto相当于平方剩余空间居中 */
/* position: absolute;
margin: auto;
top: 0;
left: 0;
right: 0;
bottom: 0; */
/* 方法三:利用display:table-cell */
/* display: inline-block */
/* 方法五:计算父盒子与子盒子的空间距离(这跟方法一是一个道理) */
/* (parent550-child150)÷2=200 */
/* margin-top: 200px;
margin-left: 200px; */
/* 方法六:利用transfrom */
/* position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%); */
/* 方法七:利用calc计算 */
/* top和left都是(父元素高-子元素高)÷2 */
/* position: absolute;
top:calc(200px);
left: calc(200px); */
}
</style>
如何比较React和Vue?
1、监听数据变化的实现原理不同
-
Vue通过getter/setter以及一些函数,能精确知道数据变化
-
React默认是通过比较引用的方式(diff)进行的,React不精确监听数据变化
2、数据流不同
-
Vue2.0可以通过props实现双向绑定,用vuex单向数据流的状态管理框架
-
React不支持双向绑定,提倡单项数据流,Redux单向数据流的状态管理框架
3、组件通信的区别
-
Vue三种组件通信方法:
父组件通过props向子组件传递数据或回调
子组件通过事件event向父组件发送数据或回调
通过provide/inject实现父组件向子组件传入数据,可跨层级
-
React三种组件通信方法:
父组件通过props向子组件传递数据
React不支持子组件像父组件发送数据,而使用的是回调函数
通过 context实现父组件向子组件传入数据, 可跨层级
4、模板渲染方式不同
-
表面上来看:
React通过JSX渲染模板
Vue通过HTML进行渲染
-
深层上来看:
React是通过原生JS实现模板中常见语法,如:插件,条件,循环
Vue是与组件JS分离的单独模板,通过指令实现,如:v-if
5、模板中使用的数据
-
React里模板中使用的数据可以直接import的组件在render中调用
-
Vue里模板中使用的数据必须要在this上进行中转,还要import一个组件,还要在components中声明
6、渲染过程不同
-
Vue不需要渲染整个组件树
-
React状态改变时,全部子组件重新渲染
7、框架本质不同
-
Vue本质是MVVM框架,由MVC发展而来
-
React是前端组件化框架,由后端组件化发展而来
8、Vuex和Redux的区别
-
Vuex可以使用dispatch、commit提交更新
-
Redux只能用dispatch提交更新
9、组合不同功能方式不同
-
Vue组合不同功能方式是通过mixin,可以帮我定义的模板进行编译、声明的props接收到数据….
-
React组合不同功能方式是通过HoC(高阶组件),本质是高阶函数
谈谈你对MVVM的理解
MVVM:MVVM是Model-View-ViewModel的简写
M指的是模型,V指的是视图,VM指的是视图模型
模型指的是后端传递的数据
视图指的是所看到的页面,试图模型mvvm模式的核心,它是连接view和model的桥梁。
它有两个方向:一是将模型转化成试图,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。二是将视图转化为模型,即将所看到的页面转化成后端数据。
视图模型指的是界面和对应的model,因为数据库结构不能直接跟界面控件一一对应,所以,需要再定义一个数据对象专门对应view上的控件,而ViewModel的职责就是把model对象封装成可以显示和接受输入的界面数据对象
谈谈你对浅拷贝和深拷贝的理解
比如:
A复制了B,B被修改后
A要是随着B的变化而变化,那就是浅拷贝
要是B改变了,A不变,那就是深拷贝
浅拷贝:
拷贝一层,对象级别的则拷贝引用
深拷贝:
拷贝多层,每个层级的属性都会拷贝
vue组件间的6种通信方法
父向子传递数据通过props
子向父传递是通过$emit、event
子实例访问父实例通过$parent
父实例访问子实例通过$children
$attrs用父组件传递数据给子组件或孙组件
(包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外))
listeners用父组件传递数据给子组件或孙组件
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器
祖先组件通过provider提供变量给子孙组件
子孙组件通过inject注入变量给祖先组件
ref用来访问组件实例
emit用于父子、隔代、兄弟组件通信
on用于父子、隔代、兄弟组件通信
vuex用来作为兄弟之间和跨级之间的通信
call,apply和bind相同点和区别?
相同点:
都是用来改变函数的this对象的指向的
第一个参数都是this要指向的对象
都可以利用后续参数传参
区别:
call、apply的区别:接受参数的方式不一样
bind:不立即执行。而apply、call 立即执行
谈谈防抖和节流
如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果
防抖:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时
节流:当持续触发事件时,保证一定时间段内只调用一次事件处理函数,原理是通过判断是否到达一定时间来触发函数。
节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现
给数字增加“逗号”分隔?
例如输入: ‘“123456789.888”’ 输出:123,456,789.888
var rmb = 123456789.888;
var retRmb = rmb.toFixed(3).replace(/\d{1,3}(?=(\d{3})+(\.\d*)?$)/g, '$&,');
console.log(retRmb);
描述一下vue响应原理?
Vue响应式底层实现方法是Object.defineProperty()方法,该方法中存在一个getter和setter的可选项,可以对属性值的获取和设置造成影响
Vue中编写了一个wather来处理数据
在使用getter方法时,总会通知wather实例对view层渲染页面
同样的,在使用setter方法时,总会在变更值的同时,通知wather实例对view层进行更新
js中有几种判断数据类型的方法?
js的基本数据类型:
number、string、boolean、null、undefined、symbol(es6新增)
js的引用数据类型:
object、function
1、通过typeof判断
通过typeof无法判断出数组、对象、null的具体类型
console.log(typeof 10); //number
console.log(typeof NaN); //number
console.log(typeof "10"); //string
console.log(typeof false); //boolean
console.log(typeof Symbol()); //symbol
console.log(typeof function () {}); //function
console.log(typeof []); //object
console.log(typeof {}); //object
console.log(typeof null); //object
console.log(typeof undefined); //undefined
2、通过constructor判断
通过constructor判断数组和对象,但无法判断null,因为null不是对象 没有construtor
console.log("".constructor == String); //true
console.log(false.constructor == Boolean); //true
console.log(new Number(10).constructor == Number); //true 这里要使用对象的方式
console.log([].constructor == Array); //true
console.log({}.constructor == Object); //true
3、通过instanceof判断
instanceof是用于判断A是否为B的实例,因为instanceof是基于原型链进行检测的,所以可以通过instanceof检测数组,但是要检测对象就不是那么准确了,因为数组对象也是Object
console.log([] instanceof Array); //true
console.log([] instanceof Object); //true
console.log({} instanceof Array); //false
console.log({} instanceof Object); //true
4、通过Object.prototype.toString.call判断
都能判断出来,包括unll undefined
let getType = Object.prototype.toString;
console.log(getType.call(undefined)); //[object Undefined]
console.log(getType.call(null)); //[object Null]
console.log(getType.call([])); //[object Array]
console.log(getType.call({})); //[object Object]
js中事件传递有哪几个阶段?
捕获阶段:先由文档的根节点document往事件触发对象,从外向内捕获事件对象
目标阶段:到达目标事件位置(事发地),触发事件
冒泡阶段:再从目标事件位置往文档的根节点方向回溯,从内向外冒泡事件对象
谈谈对this的理解?
this表示当前对象,this的指向是根据调用的上下文来决定的,默认指向window对象,指向window对象时可以省略不写
调用的上下文环境包括全局和局部
全局环境:
全局环境就是在里面,这里的this始终指向的是window对象
局部环境:
在全局作用域下直接调用函数,this指向window
对象函数调用,哪个对象调用就指向哪个对象
使用new实例化对象,在构造函数中的this指向实例化对象
使用call或apply改变this的指向
其它:
用于区分全局变量和局部变量,需要使用this
返回函数当前的对象
浏览器地址输入URL到看到界面经历了哪些?
- 输入网址
- 缓存解析
- 域名解析
- tcp连接,三层握手
- 页面渲染
详细介绍一下前端缓存
前端缓存主要分为HTTP缓存和浏览器缓存,
其中HTTP缓存是在HTTP请求传输时用到的缓存,
主要在服务器代码上设置,而浏览器缓存则主要由前端js上进行设置
你知道闭包吗?平时工作中又有用到过吗?适用于什么场景?
闭包就是能够读取其他函数内部变量的函数,也就是个函数,只不过是处于其他函数内部而已
由于在javascript中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成“定义在一个函数内部的函数“
所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁
闭包可以读取函数内部的变量,和让这些变量的值始终保持在内存中
适用场景:
采用函数引用方式的setTimeout调用
将函数关联到对象的实例方法
封装相关的功能集
说说JS中原型链与继承
每个构造函数都有一个原型对象,
原型对象都包含一个指向构造函数想指针(constructor),而实例对象都包含一个指向原型对象的内部指针(proto)。
如果让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针(proto),另一个原型也包含着一个指向另一个构造函数的指针(constructor)。
假如另一个原型又是另一个类型的实例……这就构成了实例与原型的链条
原型链是实现继承的主要方法
你了解前端跨域吗?有哪些方法可以处理跨域?
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源
跨域的解决方案:
- 通过jsonp跨域
- document.domain + iframe跨域
- location.hash + iframe
- window.name + iframe跨域
- postMessage跨域
- 跨域资源共享(CORS)
- nginx代理跨域
- nodejs中间件代理跨域
- WebSocket协议跨域
Proxy与Object.defineProperty的优缺点?
Proxy的优点:
可以直接监听对象而非属性,并返回一个新对象
可以直接监听数值的变化
拦截方法较多
可以劫持整个对象,并返回一个新对象
Proxy作为新标准将受到浏览器厂商重点持续的性能优化
Proxy的缺点:
Proxy是es6提供的新特性,兼容性不好
Object.defineProperty的优点:
兼容性好,支持IE9
IE9以下的版本不兼容
Object.defineProperty的缺点:
无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应
只能劫持对象的属性,我们需要对每个对象的每个属性进行遍历
在vue2.0中,是通过递归+遍历data对象来实现对数据的监控的,
如果属性值也是对象,那么需要深度遍历,显然如果能劫持一个完整的对象才是最好的选择,
所以在vue2.0中选Object.defineProperty,
当时虽然es6的新属性出现了Proxy,但是兼容性不好,最主要的是这个属性无法用polyfill来兼容,
但是在vue3.0的版本中会有效的提供兼容解决方案
在vue3.0中,Proxy不用像Object.defineProperty给属性都设置set,get的方法,所以Proxy不会触发循环调用
Vue中的key的作用?
key的作用是为了在diff算法执行时更快的找到对应的节点,提高diff速度
vue组件高度复用增加key可以标识组件的唯一性,为了更高效更新虚拟DOM
谈谈 Vue 响应式原理?
Vue响应式底层实现方法是Object.defineProperty()方法,该方法中存在一个getter和setter的可选项,可以对属性值的获取和设置造成影响
Vue中编写了一个wather来处理数据
在使用getter方法时,总会通知wather实例对view层渲染页面
同样的,在使用setter方法时,总会在变更值得同时,通知wather实例对view层进行更新
Vue 组件 data 选项为什么一定要是函数?
如果data是一个函数的话,这样没复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用一份data,就会造成一个变了全都会变得结果
所以说vue组件的data必须是函数。这都是因为js的特性带来的,跟vue本身设计无关。
js本身的面向对象编程也是基于原型链和构造函数,应该会注意原型链上添加一般都是一个函数方法而不会去添加一个对象了
谈谈 Vue 的渲染过程?
1、把模板编译为render函数
2、实例进行挂载, 根据根节点render函数的调用,递归的生成虚拟dom
3、对比虚拟dom,渲染到真实dom
4、组件内部data发生变化,组件和子组件引用data作为props重新调用render函数,生成虚拟dom, 返回到步骤3
你对Webpack4了解吗?可以说说它的打包原理吗?
webpack是一个打包模块化JavaScript的工具,在webpack里一切文件皆模块,通过Loader转换文件,通过Plugin注入钩子,最后输出由多个模块组合成得文件,webpack专注于构建模块化项目
1、利用babel完成代码转换及解析,并生成单个文件的依赖模块Map
2、从入口开始递归分析,并生成整个项目的依赖图谱
3、将各个引用模块打包为一个立即执行函数
4、将最终的bundle文件写入bundle.js中
用CSS画一个三角形的原理?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
</head>
<body>
<div id="app"></div>
</body>
</html>
<style>
#app{
width: 0;
height: 0;
border: 93px solid;
border-color: red green yellow transparent;
}
</style>
这里主要用到了CSS3的一个transparent属性
transparent是透明的意思,在CSS3以后,支持所有的coloe属性
画的三角形其实border的宽度加上颜色来控制的,其实三角形的形成不过是,其它的边框颜色设置成了透明了而已
CSS3有哪些新特性?
-
CSS3的选择器:
E:last-child匹配父元素的最后一个子元素E
E:nth-child(n)匹配父元素的第n个子元素E
E:nth-last-child(n)匹配父元素的倒数第n个子元素E
-
@Font-face特性
-
圆角(border-radius)
-
多列布局
-
阴影(Shadow)
-
CSS3的渐变效果
-
CSS弹性盒子模型
-
transition对象变换时的过渡效果
-
transforms 2D转换效果
-
Animation动画效果
position有哪些值?分别代表什么作用?
static:默认值 没有定位,元素出现在正常的流中
absolute:绝对定位 相对于static定位以外的第一个父元素进行定位
relative:相对定位 生成相对定位的元素,相对于其正常位置进行定位
fixed:固定定位 生成绝对定位的元素,相对于浏览器窗口进行定位
你对缓存有了解吗?
缓存是建立一个函数的过程,这个函数能够记住之前计算的结果或值。使用缓存函数是为了避免在最后一次使用相同参数的计算中已经执行的函数的计算。这节省了时间,但也有不利的一面,即我们将消耗更多的内存来保存以前的结果
js有哪些继承方式?
原型链继承:父类的实例作为子类的原型
借用构造函数继承:复制父类的实例属性给子类
组合继承:调用父类构造函数,继承父类的属性,通过将父类实例作为子类原型,实现函数复用
原型式继承:不自定义类型的情况下,临时创建一个构造函数,借助已有的对象作为临时构造函数的原型,然后在此基础实例化对象,并返回
寄生式继承:其实就是在原型式继承得到对象的基础上,在内部再以某种方式来增强对象,然后返回
寄生组合继承:通过寄生的方式来修复组合式继承的不足,完美的实现继承
最后
如果本文对你有帮助得话,给本文点个赞❤️❤️❤️
欢迎大家加入,一起学习前端,共同进步!