前端科举八股文-JAVASCRIPT篇
- Js的变量类型,区别是什么
- 平时有用过symbol吗
- 函数闭包的理解?
- js的原型链? Function === Function.constructor 返回值?
- promise的出现是为了解决什么问题?
- 前端中的事件流
- 事件委托?
- js的new操作符做了哪些事情
- bind call apply的区别
- 浏览器的垃圾回收机制
- 如何判断是不是数组?
- 垃圾回收机制
- mouseover和mouseenter的区别
- 前端的模块化
- Js代码的执行顺序
- 讲一下你对懒加载和预加载的理解
- Js的new操作符?
- Js的防抖和节流
- 深拷贝,浅拷贝的区别?
- 如何实现深拷贝
- 如何实现所有对象的深拷贝?
- 数组去重方法?
- 说一下virtual dom
- 讲一下js实现继承的几种方式
- 0.1+0.2 === 0.3? 为什么 如何解决
- Typeof NaN等于什么? NaN === NaN ?
- == , ===和object.is的区别?
- 箭头函数和普通函数的区别?
- map和weakMap的区别?
Js的变量类型,区别是什么
基本number boolean undefined null string es6新增的 symbol
引用 object array es6新增的 set map weakSet weakMap
区别 基本类型将值直接存储在栈中,引用对象类型将值存储在堆中,在栈中存储的只是指向值的指针。
平时有用过symbol吗
symbol是es6新增的一个基本对象类型。它表示一个独一无二的值。
日常开发中我用它来清除魔法字符串。比如我们再给一个业务常量赋值初始值的时候并不关心它的初始值是什么,只需要保证这个值不和其他值重复。这时候就可以使用symbol来初始化这个常量。
const student_1 = 'zhangsan'
const student_2 = 'lisi'
// 如果这个时候有个学生名字叫章三
const student_3 = 'zhangsan' // 这行代码容易出事
我们定义常量并不关心常量的值,只需要保证他们都不重复时可以使用symbol
const student_1 = Symbol()
const student_2 = Symbol()
const student_3 = Symbol()// 如果觉得这种写法语意化不强,实在是不好理解也可以这样改写const student_1 = Symbol('zhangsan')
const student_2 = Symbol('lisi')
const student_3 = Symbol('zhangsan')
由于Symbold的特殊性,此时的两个symbol值也不会相等 student_1 !== student_3
还有一个用法是,使用symbol定义的对象属性不能被遍历方法遍历。可以利用这个特性封装一些不需要对外暴露的对象属性或者方法。使代码更加优雅
const me = {name:'wxs',age:14,[Symbol('hobby')]:'code'}Object.keys(me) // ['name','age']
函数闭包的理解?
闭包的本质就是函数内部的函数,内部函数可以访问到外部函数里面的变量或者方法,同时暴露给外部调用. 因为外部函数是形成了局部作用域的,在函数外部是无法直接访问的.
const fn = () => {let name = '1233'}由于函数局部作用域的特性,函数外部是无法访问这个name属性的。
如果要访问函数内部的属性或者方法可以使用闭包
const fn = () => {let name = '1233'const returnName = () => name}
坏处 容易造成内存泄漏.因为内部函数始终保存了外部函数内部变量的引用.不会被浏览器GC机制所回收.
js的原型链? Function === Function.constructor 返回值?
当我们调用一个对象上的属性或者方法时,js会在当前对象上查找,如果查找不到就沿着对象的原型链也就是沿着__proto__一直往上找,一直找到object.prototype.proto,如果没有此属性或者方法才会返回undefined,一般来说构造函数的实例的constructor指针会指向构造函数本身,但是Function是一个特例,Function === Function.constructor 返回是true。可以这样理解,Function也是其构造函数(Function)的一个实例。如果觉得有点绕,可以结合这篇博客食用
promise的出现是为了解决什么问题?
为了解决异步回调地狱,在promise之前,如果一个异步函数依赖另一个异步函数的返回值,那么就要在回调里面嵌套回调,如果层级比较多的情况下代码就会变得十分难以维护。promise可以将异步回调代码以一种“同步”形式展示,使代码更可读。
sendRequest('请求参数',(res) => {sendRequest_2(res,(res_2) => {sendRequest_3(res_2,(res_3) => {// do something}})})
// 在promise出现之前这种就是异步回调地狱 (嵌套过深,导致代码难看且难以维护)
// 在promise出现之后可以将上述代码写成同步的形式(本质上还是异步代码)const sendAjax = (params) => {return new Promise((resolve,reject) => {sendRequest(params,(res) => {resolve(res)})})
}
sendAjax('请求参数').then(res => {sendAjax(res).then(res_1 => {sendAjax(res_1)})
})
前端中的事件流
先事件捕获,再事件冒泡。事件捕获和事件冒泡顺序相反,事件捕获是从先外层再内层,事件冒泡是先内层目标事件对象再冒泡到外层。浏览器的默认行为是事件冒泡。可以通过addeventlistener的第三个参数来决策浏览器行为是事件捕获还是事件冒泡。
事件委托?
它是利用浏览器的冒泡机制的一个代码技巧,比如我们有一个list需要绑定事件,不需要在每一个li上去绑定事件,直接在它的父元素ul上去绑定事件就可以,这就是事件委托.
js的new操作符做了哪些事情
首先会新建一个空对象,然后通过bind或者apply执行构造函数将构造函数的this指向这个空对象,然后改写空对象的constructor属性和__proto__属性.最后return这个对象.
bind call apply的区别
三者都是js中改变this指向的api,call 和bind是立即执行,call的参数是,分割,bind的参数是数组. Apply的参数是数组,返回的是一个函数 不会立即执行.
浏览器的垃圾回收机制
之前是有两种,一个计数引用,一个标记清楚.后来由于对象之间会循环引用的关系,计数引用的计数量永远会是1,所以现在主流的垃圾回收机制是标记清除,一旦浏览器判断这个对象被释放就会进行垃圾回收.
如何判断是不是数组?
isArray
typeof + 是否具有array的方法
原型链判断 数组的.proto === Array.prototype
垃圾回收机制
浏览器的垃圾回收机制是指浏览器会周期性的去释放一些已经不再使用的js内存.
目前最流行的垃圾回收机制是标记清除. 当某一个变量被声明或者被使用的时候就会被标记进入环境,当使用完成就会删除其标记,在垃圾回收的时候就会把这些不再使用的变量占用的内存给回收掉.
还有一种主流的方式是计数引用,它会记录每一个变量被引用的数量,在垃圾回收的时候只会清除那些引用计数为0的变量内存.但是这种方式有缺陷.在两个对象循环引用的时候计数引用永远不会清零
mouseover和mouseenter的区别
Mouseover进入目标元素的子元素也会重复触发.而mouseenter只有进入目标元素的时候才会触发.
前端的模块化
(早期的CMD,AMD没用过,略去不表),这里就答ES module和node的commonjs规范.
CMD,AMD规范没使用过,不太了解,node主要遵循的是commomjs规范,前端浏览器主要使用的node模块化规范.
它们之间的不同第一个是使用方式不同,node是通过require和export来导入导出,每一个文件都是module的一个实例,内置了require和export变量.node模块是通过export 或者export default导出,import来导入.
Node模块是运行时加载,导出的是整个node对象,然后按需取所需的变量
Es6模块是静态加载,在编译的时候就完成了模块的加载.所以可以按需导出使用的变量.
Node模块导出的是值的浅拷贝,不会污染原模块
Es6模块导出的是值的引用,改动会影响到原模块
Js代码的执行顺序
Js代码的执行顺序和eventloop模型有关.js执行代码会先把代码中的同步代码推入同步队列,把异步任务的微任务推入微任务队列,再把宏任务推入宏任务队列.执行顺序是先清空同步任务,再清空微任务队列,再执行宏任务的第一个任务,再循环清空同步队列,微任务队列.这就是eventloop模型.
常见的微任务 promise.then 浏览器的oberveMutation事件等
常见的宏任务 定时器 延时器 requestAnimationFrame
讲一下你对懒加载和预加载的理解
两者都是提升用户体验. 以图片为例,懒加载是不加载不在视觉区域里面的图片,等到图片进入用户视口了再进行加载.懒加载可以减轻服务器压力,因为减少了初始请求.
而预加载相反,它是指预先加载资源,以便直接使用. 它是提前加载了不需要使用的资源.增加了服务器的压力.
Js的new操作符?
首先会新建一个空对象,然后执行构造函数,通过bind或者call将空对象的this指向这个空对象,然后重写对象的__proto__和constructor属性.最后return 这个对象.
Js的防抖和节流
在js中有一些高频触发的操作,比如window.resize和scroll事件等等,在这种事件中去执行一些高开销的操作比如发送请求是十分消耗性能的. 防抖是把一些高频触发的事件合并成一次.节流的思想是在防抖的基础上,将多次事件触发合并成少次触发.每隔x秒执行一次.
深拷贝,浅拷贝的区别?
深拷贝拷贝值,浅拷贝拷贝引用
如何实现深拷贝
1、递归实现
2、JSON序列化方法,有缺陷,null,正则和函数不能拷贝
如何实现所有对象的深拷贝?
对正则,函数,日期使用valueOf来克隆原始值进行拷贝
数组去重方法?
1、遍历,推入一个新数组,如果include新值就不推入
2、遍历,将每一个值作为对象的key,利用对象key不能重复的特点进行去重
3、利用ES6的新api Set
说一下virtual dom
虚拟dom树,是vue框架和react框架实现其性能优化的基础.这两个框架的视图都和数据强相关的,但是每次数据改变引发浏览器回流重新渲染的开销是很大的.所以vue和react都采取了virtual dom+diff算法来比较视图更新前后需要部分更新的部分节点来降低浏览器渲染页面的开销来实现性能优化的目的.
讲一下js实现继承的几种方式
ES6之前的继承方式主要有构造继承和原型继承.
构造继承,实现方式是在子构造函数中去调用通过call或者apply调用 父类构造函数,把父类构造函数的this指向改到子类从而实现继承. 优势是可以传参,缺点是不能继承原型上的属性或者方法,同时所有子类是继承同一个父类中的引用对象属性.会造成污染
还有一种构造继承. 是将父类的一个实例赋值给子类的prototype实现继承,但是这样不能传递参数. 所以可以采取组合继承来把这两种继承方式组合起来.
Es6之后可以用类+extends来实现继承.
0.1+0.2 === 0.3? 为什么 如何解决
首先返回时false,是因为计算机在底层计算是按2进制去运算的,而0.1和0.2用二进制表示都是无限循环小数,计算出来的和是不等于0.3的. 解决方法可以用toFix来计算.或者用ES6新的常量Number. EPSILON来判断两个数之间的系统误差是否小于这个值,只要小于这个系统误差就表示在合理范围内.
Typeof NaN等于什么? NaN === NaN ?
NaN的类型是number, NaN 不等于自身,是js类型中唯一一个不等于自身的值.
== , ===和object.is的区别?
双等号在比较之前会进行隐式转化,三等号不会进行转换,直接强比较,object.is比较方法和三等号一致,在特殊比较上做了兼容,比如 +0 不等于 -0 ,NaN 等于NaN.
箭头函数和普通函数的区别?
最大的区别是箭头函数的this是指向声明域,是固定的,而普通函数的this永远是指向调用者.箭头函数没有prototype,没有argument,不能用做构造函数
map和weakMap的区别?
Map的key可以是任何值,weakMap的key只能是对象.
weakMap的设计初衷是因为使用map在对象上存放一些数据时,会形成对这个对象的引用,导致这个对象不会被GC回收,需要手动去清除.而weakMap对作为key的对象是弱引用.不会影响GC机制回收该对象. 而同时也因为对key的值是弱引用,weakMap是没有size对象的.因为它的size具有不确定性(因为GC回收机制执行时机的不确定性).