🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 1.JS 数字精读丢失问题 参考链接:[https://www.cnblogs.com/snandy/p/4943138.html](https://www.cnblogs.com/snandy/p/4943138.html) ## 常见的数字精读丢失的情况 1.两个浮点数相加 ```js console.log(0.1 + 0.2 !== 0.3) // true console.log(0.1 + 0.2) // 0.30000000000000004 ``` 2.大整数运算(多少位?) ```js console.log(9999999999999999 === 10000000000000001) // true var x = 9007199254740992 console.log(x + 1 === x) // true ``` 3.toFixed() 没有四舍五入,此外 toFixed 还有一些兼容性问题 ```js console.log(1.335.toFixed(2)) // 1.33 ``` ## 原因 JS 遵循[IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point)规范,采用双精度存储(double precision),占用 64 bit。如图 ![](https://box.kancloud.cn/20cfb7d2c57d2aa24f53aeb9865a1ab6_1098x78.png) - 1位用来表示符号位 - 11位用来表示指数 - 52位表示尾数 浮点数,比如 ```js 0.1 >> 0.0001 1001 1001 1001…(1001无限循环) 0.2 >> 0.0011 0011 0011 0011…(0011无限循环) ``` 此时只能模仿十进制进行四舍五入了,但是二进制只有 0 和 1 两个,于是变为 0 舍 1 入。这即是计算机中部分浮点数运算时出现误差,丢失精度的根本原因。 大整数的精度丢失和浮点数本质上是一样的,尾数位最大是 52 位,因此 JS 中能精准表示的最大整数是 Math.pow(2, 53),十进制即 9007199254740992。 大于 9007199254740992 的可能会丢失精度 ```js 9007199254740992 >> 10000000000000...000 // 共计 53 个 0 9007199254740992 + 1 >> 10000000000000...001 // 中间 52 个 0 9007199254740992 + 2 >> 10000000000000...010 // 中间 51 个 0 ``` ## 解决方案 对于整数,前端出现问题的几率可能比较低,毕竟很少有业务需要需要用到超大整数,只要运算结果不超过 Math.pow(2, 53) 就不会丢失精度。(如果遇到一般会用字符串来替代?) 对于小数,前端出现问题的几率还是很多的,尤其在一些电商网站涉及到金额等数据。解决方式:把小数放到位整数(乘倍数),再缩小回原来倍数(除倍数) ```js // 0.1 + 0.2 (0.1*10 + 0.2*10) / 10 == 0.3 // true ``` 对于 toFixed 方法出现的问题 ```js /** * * @param {*} num 需要转化的数值 * @param {*} s 保留到小数点后第几位 */ function toFixed (num, s) { let times = Math.pow(10, s) let des = num * times + 0.5 des = parseInt(des, 10) / times return des + '' } console.log(toFixed(1.335, 2)) // 1.34 ``` >TODO:这里的解决方案感觉不是很好,有空再找找... # 2.几个细节题 ```js // this 指向 var obj = { name: 'foo', fun1: function () { console.log(this.name) }, fun2: () => { console.log(this.name) } } var fun3 = obj.fun1 fun3() obj.fun1() obj.fun2() ``` ```js // 事件循环 - resolve 之后是否会继续往下执行? setTimeout(function () { console.log('a') }) var p = new Promise(function (resolve, reject) { console.log('b') resolve() console.log('c') }) p.then(function () { console.log('d') }) console.log('e') ``` ```js // 原型链 构造函数的 __proto__ 指向什么呢? Function.prototype.a = 1 Object.prototype.a = 2 Object.a = ? ``` # 3.模拟实现 lodash 中的 \_.get() 函数 模拟实现 lodash 中的 \_.get() 函数,实现如下传入参数取值效果 ```js function get() { // 请补全函数参数和实现逻辑 } const obj = { selector: { to: { toutiao: 'FE coder' } }, target: [1, 2, { name: 'byted' }] }; // 运行代码 get(obj, 'selector.to.toutiao', 'target[0]', 'target[2].name') // 输出结果:// ['FE coder', 1, 'byted'] ``` 勉强实现了 ```js function get (obj, ...args) { const res = [] // 存储返回结果 const params = args params.forEach(item => { // 'selector.to.toutiao' , 'target[0]', 'target[2].name' let splitItem = item.split('.') // -> ['selector', 'to', 'toutiao'], ['target[0]'], ['target[2], 'name'] let temp = null splitItem.forEach(value => { let index = value.indexOf('[') // case of 'target[0]' if (index !== -1) { let num = parseInt(value.substring(index + 1)) // 数组下标 Number 类型 let attr = value.substring(0, index) temp = temp === null ? obj[attr][num] : temp[attr][num] } else { temp = temp === null ? obj[value] : temp[value] // obj.selector.to.toutiao } }) res.push(temp) }) return res } console.log(get(obj, 'selector.to.toutiao', 'target[0]', 'target[2].name')) ``` # 4.找出页面出现最多的标签 你知道 `document.getElementsByTagName('*')`的用法吗? ```js // 获取元素列表,以键值对的形式存储为一个对象 function getElements () { // 如果把特殊字符串 "*" 传递给 getElementsByTagName() 方法 // 它将返回文档中所有元素的列表,元素排列的顺序就是它们在文档中的顺序。 // 返回一个 HTMLCollection - 类数组对象 const nodes = document.getElementsByTagName('*') const tagsMap = {} for (let i = 0, len = nodes.length; i < len; i++) { let tagName = nodes[i].tagName if (!tagsMap[tagName]) { tagsMap[tagName] = 1 } else { tagsMap[tagName] ++ } } return tagsMap } // n 为要选取的标签个数 - 即出现次数前 n 的标签名 // 将上面的方法获取的对象的键值对取出组成数组,按出现次数排序 function sortElements (obj, n) { const arr = [] const res = [] for (let key of Object.keys(obj)) { arr.push({ tagName: key, count: obj[key] }) } // 冒泡 for (let i = arr.length - 1; i > 0; i--) { for (let j = 0; j < i; j++) { if (arr[j].count < arr[j + 1].count) { // 升序 swap(arr, j, j + 1) } } } for (let i = 0; i < n; i++) { res.push(arr[i].tagName) } return res } function swap (arr, index1, index2) { let temp = arr[index1] arr[index1] = arr[index2] arr[index2] = temp } let res = sortElements(getElements(), 2) console.log(res) ``` # 5.细节问题 以下代码的执行结果是什么? ```js function func() { var a = b = 1; } func(); console.log(a); console.log(b); ``` 答案:error,1 解析:b 没有 var 为全局变量,var a = b 局部变量外部访问不到 # 6.fetch 本身不支持 timeout,能否对其进行封装使其支持 timeout? 参考:[https://imweb.io/topic/57c6ea35808fd2fb204eef63](https://imweb.io/topic/57c6ea35808fd2fb204eef63) ```js /** * 包装 fetch 方法,使其支持 timeout * @param {Promise} fetch_promise fetch 请求 * @param {Numebr} timeout 超时时间设置 */ function _fetch (fetch_promise, timeout) { var abort_fn = null // 这是一个可以被 reject 的 promise var abort_promise = new Promise(function (resolve, reject) { abort_fn = function () { reject('abort promise') } }) // 这里使用 Promise.race, 以最快 resolve 或 reject 的结果来传入后续绑定的回调 var abortable_promise = Promise.race([ fetch_promise, abort_promise ]) setTimeout(() => { abort_fn() }, timeout) return abortable_promise } // usage _fetch(fetch('http://test.com'), 2000) .then(res => { console.log(res) }) .catch(err => { console.log(err) }) ```