一名【合格】前端工程师的自检清单
(语法和API篇)
(数据结构和算法篇)
(浏览器API篇)
(JavaScript编码能力篇)
(浏览器原理篇)
###一、JavaScript基础
变量和类型
1.JavaScript规定了几种数据类型?
String、Number、Boolean、Null、undefined、Symbol、Object
2.JavaScript对象的底层数据结构是什么?
js基本类型数据都是直接按值存储在栈中的,每种类型的数据占用的内存空间的大小是确定的,并由系统自动分配和自动释放。这样带来的好处就是,内存可以及时得到回收,相对于堆来说 ,更加容易管理内存空间。
js引用类型数据被存储于堆中 。其实,说存储于堆中,也不太准确,因为,引用类型的数据的地址指针是存储于栈中的,当我们想要访问引用类型的值的时候,需要先从栈中获得对象的地址指针,然后,在通过地址指针找到堆中的所需要的数据。
3.Symbol类型在实际开发中的应用、可手动实现一个简单的Symbol
4.JavaScript中的变量在内存中的具体存储形式
5.基本类型对应的内置对象,以及他们之间的装箱拆箱操作
6.理解值类型和引用类型
7.null和undefined的区别
在JavaScript中,将一个变量赋值为undefined或null,老实说,几乎没区别。那为什么还要设计两个呢?由于一些历史原因。其次,JavaScript的最初版本没有包括错误处理机制,发生数据类型不匹配时,往往是自动转换类型或者默默地失败。Brendan Eich觉得,如果null自动转为0,很不容易发现错误。因此,Brendan Eich又设计了一个undefined。
null表示"没有对象",即该处不应该有值
undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。
8.至少可以说出三种判断JavaScript数据类型的方式,以及他们的优缺点,如何准确的判断数组类型
typeof: 缺点不能细分对象、数组,并且null返回’object’
instanceof:([1,2,3] instanceof Array) 判断是否是某个类的实例,所以左侧要是一个对象 缺点:不能判断基本数据类型
constructor: ([1,2,3] .constructorArray)constructor是prototype对象上的属性,指向构造函数。根据实例对象寻找属性的顺序,若实例对象上没有实例属性或方法时,就去原型链上寻找,因此,实例对象也是能使用constructor属性的。 缺点:不能判断null、undefined,constructor 所指向的的构造函数 可以被修改的。
Object.prototype.toString.call(): Object.prototype.toString对任何变量都会返回这样一个字符串"[object class]",class 就是 JS 内置对象 构造函数的名字。 call是用来改变调用函数作用域的。
9.可能发生隐式类型转换的场景以及转换原则,应如何避免或巧妙应用
算术运算符:+号两边出现字符串,结果转换成字符串拼接;-、 *、/、 % 将字符转换成数字,再进行正常的运算,结果必为数值
关系(比较)运算符:<、<=、>、>=、!=、 任意一边出现数值,就会把另一边转成数值,再进行比较;如果都是字符,才是字符的比较规则:逐位比较(ASCII值),得到结果就停止比较;
isNaN( ):使用number()转换成数字
判断语句if():判断语句的判断条件,表达式的结果就是真和假,隐式转换,将其他转成布尔值
10.出现小数精度丢失的原因,JavaScript可以存储的最大数字、最大安全数字,JavaScript处理大数字的方法、避免精度丢失的方法
出现小数精度丢失的原因:JS 遵循 IEEE 754 规范,采用双精度存储),占用 64 位。二进制只有 0 和 1 两个,超出64位于是变为 0 舍 1 入。这即是计算机中部分浮点数运算时出现误差,丢失精度的根本原因。
JavaScript可以存储的最大数字、最大安全数字:(9007199254740991)2的53次方减一
JavaScript避免精度丢失的方法
1、把小数放到位整数(乘倍数),再缩小回原来倍数(除倍数)
/**
* floatObj 包含加减乘除四个方法,能确保浮点数运算不丢失精度
*
* 我们知道计算机编程语言里浮点数计算会存在精度丢失问题(或称舍入误差),其根本原因是二进制和实现位数限制有些数无法有限表示
* 以下是十进制小数对应的二进制表示
* 0.1 >> 0.0001 1001 1001 1001…(1001无限循环)
* 0.2 >> 0.0011 0011 0011 0011…(0011无限循环)
* 计算机里每种数据类型的存储是一个有限宽度,比如 JavaScript 使用 64 位存储数字类型,因此超出的会舍去。
舍去的部分就是精度丢失的部分。
*
* ** method **
* add / subtract / multiply /divide
*
* ** explame **
* 0.1 + 0.2 == 0.30000000000000004 (多了 0.00000000000004)
* 0.2 + 0.4 == 0.6000000000000001 (多了 0.0000000000001)
* 19.9 * 100 == 1989.9999999999998 (少了 0.0000000000002)
*
* floatObj.add(0.1, 0.2) >> 0.3
* floatObj.multiply(19.9, 100) >> 1990
*
*/
var floatObj = function() {
/*
* 判断obj是否为一个整数
*/
function isInteger(obj) {
return Math.floor(obj) === obj
}
/*
* 将一个浮点数转成整数,返回整数和倍数。如 3.14 >> 314,倍数是 100
* @param floatNum {number} 小数
* @return {object}
* {times:100, num: 314}
*/
function toInteger(floatNum) {
var ret = {times: 1, num: 0}
var isNegative = floatNum < 0
if (isInteger(floatNum)) {
ret.num = floatNum
return ret
}
var strfi = floatNum + ''
var dotPos = strfi.indexOf('.')
var len = strfi.substr(dotPos+1).length
var times = Math.pow(10, len)
var intNum = parseInt(Math.abs(floatNum) * times + 0.5, 10)
ret.times = times
if (isNegative) {
intNum = -intNum
}
ret.num = intNum
return ret
}
/*
* 核心方法,实现加减乘除运算,确保不丢失精度
* 思路:把小数放大为整数(乘),进行算术运算,再缩小为小数(除)
*
* @param a {number} 运算数1
* @param b {number} 运算数2
* @param digits {number} 精度,保留的小数点数,比如 2, 即保留为两位小数
* @param op {string} 运算类型,有加减乘除(add/subtract/multiply/divide)
*
*/
function operation(a, b, digits, op) {
var o1 = toInteger(a)
var o2 = toInteger(b)
var n1 = o1.num
var n2 = o2.num
var t1 = o1.times
var t2 = o2.times
var max = t1 > t2 ? t1 : t2
var result = null
switch (op) {
case 'add':
if (t1 === t2) { // 两个小数位数相同
result = n1 + n2
} else if (t1 > t2) { // o1 小数位 大于 o2
result = n1 + n2 * (t1 / t2)
} else { // o1 小数位 小于 o2
result = n1 * (t2 / t1) + n2
}
return result / max
case 'subtract':
if (t1 === t2) {
result = n1 - n2
} else if (t1 > t2) {
result = n1 - n2 * (t1 / t2)
} else {
result = n1 * (t2 / t1) - n2
}
return result / max
case 'multiply':
result = (n1 * n2) / (t1 * t2)
return result
case 'divide':
result = (n1 / n2) * (t2 / t1)
return result
}
}
// 加减乘除的四个接口
function add(a, b, digits) {
return operation(a, b, digits, 'add')
}
function subtract(a, b, digits) {
return operation(a, b, digits, 'subtract')
}
function multiply(a, b, digits) {
return operation(a, b, digits, 'multiply')
}
function divide(a, b, digits) {
return operation(a, b, digits, 'divide')
}
// exports
return {
add: add,
subtract: subtract,
multiply: multiply,
divide: divide
}
}();
2.toFixed(Chrome 不会四舍五入)的修复如下:
// toFixed 修复
function toFixed(num, s) {
var times = Math.pow(10, s)
// 放大time倍 +0.5为了达到四舍五入
// 例如:3.445*100+0.5 = 345 3.444*100+0.5 = 344.9 在使用parseInt达到四舍五入
var des = num * times + 0.5
des = parseInt(des, 10) / times
return des + ''
}
处理大数字的方法:①使用big-integer处理大数 ②将数字变为字符串进行处理
原型和原型链
1.理解原型设计模式以及JavaScript中的原型规则
原型规则:所有引用类型(数组、对象、函数),都具有对象特征,即可自由扩展属性;都有一个__proto__属性,属性值是原型对象;所有函数都具有prototype,值是原型对象;所有引用类型指的__proto__指向器构造函数的原型对象;当得到一个属性没有时会根据___proto__去查找。
原型对象:prototype 在js中,函数对象其中一个属性,值为原型对象。普通对象没有这个属性但是___proto__指向原型对象。原型的作用就是给这个类的每一个对象都添加一个统一的方法,在原型中定义的方法和属性都是被所以实例对象所共享。
原型链:当试图得到一个对象f的某个属性时,如果这个对象本身没有这个属性,那么会沿着__proto__查找。
2.instanceof的底层实现原理,手动实现一个instanceof
3.实现继承的几种方式以及他们的优缺点
实现继承的几种方式:
1、原型继承(继承方法)
function Parent(){
this.name = '名字'
this.age = 18
}
Parent.prototype = sun(){
console.log('唱歌')
}
function Son(){}
// 子类的原型指向父类的实例, 子类实例在调用方法的时候会通过__proto__向上查找
Son.prototype = new Son()
2、借用call继承属性
function Son(){
// 借用call 来实行构造函数
Parent.call(this)
}
3、组合继承(原型继承和借用继承一起使用)
4.至少说出一种开源项目(如Node)中应用原型继承的案例
5.可以描述new一个对象的详细过程,手动实现一个new操作符
new关键字的执行过程:1、创建一个空对象 2、改变this指向 3、向其中添加属性 4、返回这个对象
new关键字的执行过程
如果一个类返回值是一个引用类型那么就返回这个引用类型值
function mockNew(fn){
let obj = {}
let newValue = fn.call(obj)
if((typeof returnVal === 'object' && returnVal !== null) || typeof returnVal === 'function'){
return returnVal;
}
obj.__proto__ = fn.prototype
return obj
}
6.理解es6 class构造以及继承的底层实现原理
Class本质上是一个特别的函数 Class 在语法上更加贴合面向对象的写法 Class 实现继承更加易读、 易理解 更易于写 java 等后端语言的使用 本质还是语法糖, 使用 prototype
我们可以借助babel来探究es6类和继承的实现原理
作用域和闭包
1.理解词法作用域和动态作用域
2.理解JavaScript的作用域和作用域链
作用域:也就是创建函数时当前执行上下文存储变量的地方
作用域链:变量取值的过程,先在自己执行上下文中的变量对象中找看是否是自己AO中的私有变量,找不到就去上级作用域查找,有则停止,无就继续,一直找到全局作用域。
3.理解JavaScript的执行上下文栈,可以应用堆栈信息快速定位问题
4.this的原理以及几种不同使用场景的取值
5.闭包的实现原理和作用,可以列举几个开发中闭包的实际应用
闭包:函数执行形成一个全新的执行上下文,进栈执行后所创建的一些变量被外部所引用,不能出栈销毁,就形成了闭包。
作用:延长了局部变量的生命周期,保护全不被污染
缺点:函数不能被出栈销毁,频繁使用会造成内存泄漏问题。
解决方法:手动销毁 fn=null
6.理解堆栈溢出和内存泄漏的原理,如何防止
7.如何处理循环的异步操作
8.理解模块化解决的实际问题,可列举几个模块化方案并理解其中原理
执行机制
1.为何try里面放return,finally还会执行,理解其内部机制
2.JavaScript如何实现异步编程,可以详细描述EventLoop机制
ES 6以前:
- 回调函数
- 事件监听(事件发布/订阅)
- Promise对象
ES 6:- Generator函数(协程coroutine)
ES 7:- async和await
事件队列和事件循环:
js是单线程的只有等主线程执行完毕,才会去执行异步的任务。
异步任务放在任务队列中,任务队列分为微任务和宏任务 先执行微任务在执行宏任务
微任务:promise、async、await
宏任务:定时器
事件循环:主线程执行完代码后去任务队列中查找可执行异步任务,先找微任务找到执行,在查找执行,微任务没有后再找宏任务,查找执行;这一套机制称之微事件循环机制。
3.宏任务和微任务分别有哪些
宏任务:I/O 、setTimeout、setInterval、setImmediate、requestAnimationFrame
微任务:process.nextTick、MutationObserver、promise、async、await
4.可以快速分析一个复杂的异步嵌套逻辑,并掌握分析方法
5.使用Promise实现串行
6.Node与浏览器EventLoop的差异
7.如何在保证页面运行流畅的情况下处理海量数据
语法和API
1.理解ECMAScript和JavaScript的关系
1996年11月,JavaScript的创造者Netscape公司,决定将JavaScript提交给国际标准化组织ECMA,希望这种语言能够成为国际标准。所以说ECMAScript和JavaScript的关系是,前者是后者的规格(标准)。
2.熟练运用es5、es6提供的语法规范,
es5语法规范
es6语法规范1 es6语法规范1
3.熟练掌握JavaScript提供的全局对象(例如Date、Math)、全局函数(例如decodeURI、isNaN)、全局属性(例如Infinity、undefined)
4.熟练应用map、reduce、filter 等高阶函数解决问题
5.setInterval需要注意的点,使用settimeout实现setInterval
6.JavaScript提供的正则表达式API、可以使用正则表达式(邮箱校验、URL解析、去重等)解决常见问题
7.JavaScript异常处理的方式,统一的异常处理方案