代码输出结果
function runAsync (x) { const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000)) return p}Promise.race([runAsync(1), runAsync(2), runAsync(3)]) .then(res => console.log('result: ', res)) .catch(err => console.log(err))
复制代码
输出结果如下:
then 只会捕获第一个成功的方法,其他的函数虽然还会继续执行,但是不是被 then 捕获了。
冒泡排序--时间复杂度 n^2
题目描述:实现一个冒泡排序
实现代码如下:
function bubbleSort(arr) { // 缓存数组长度 const len = arr.length; // 外层循环用于控制从头到尾的比较+交换到底有多少轮 for (let i = 0; i < len; i++) { // 内层循环用于完成每一轮遍历过程中的重复比较+交换 for (let j = 0; j < len - 1; j++) { // 若相邻元素前面的数比后面的大 if (arr[j] > arr[j + 1]) { // 交换两者 [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; } } } // 返回数组 return arr;}// console.log(bubbleSort([3, 6, 2, 4, 1]));
复制代码
Vuex 有哪些基本属性?为什么 Vuex 的 mutation 中不能做异步操作?
有五种,分别是 State、 Getter、Mutation 、Action、 Module1、state => 基本数据(数据源存放地)2、getters => 从基本数据派生出来的数据3、mutations => 提交更改数据的方法,同步4、actions => 像一个装饰器,包裹mutations,使之可以异步。5、modules => 模块化Vuex
1、Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过 Action 来提交 mutation实现,这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用。2、每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。
复制代码
代码输出结果
// afunction Foo () { getName = function () { console.log(1); } return this;}// bFoo.getName = function () { console.log(2);}// cFoo.prototype.getName = function () { console.log(3);}// dvar getName = function () { console.log(4);}// efunction getName () { console.log(5);}
Foo.getName(); // 2getName(); // 4Foo().getName(); // 1getName(); // 1 new Foo.getName(); // 2new Foo().getName(); // 3new new Foo().getName(); // 3
复制代码
输出结果:2 4 1 1 2 3 3
解析:
Foo.getName(), Foo 为一个函数对象,对象都可以有属性,b 处定义 Foo 的 getName 属性为函数,输出 2;
getName(), 这里看 d、e 处,d 为函数表达式,e 为函数声明,两者区别在于变量提升,函数声明的 5 会被后边函数表达式的 4 覆盖;
** Foo().getName(),** 这里要看 a 处,在 Foo 内部将全局的 getName 重新赋值为 console.log(1) 的函数,执行 Foo()返回 this,这个 this 指向 window,Foo().getName() 即为 window.getName(),输出 1;
getName(), 上面 3 中,全局的 getName 已经被重新赋值,所以这里依然输出 1;
new Foo.getName(), 这里等价于 new (Foo.getName()),先执行 Foo.getName(),输出 2,然后 new 一个实例;
new Foo().getName(), 这 里等价于 (new Foo()).getName(), 先 new 一个 Foo 的实例,再执行这个实例的 getName 方法,但是这个实例本身没有这个方法,所以去原型链__protot__上边找,实例.protot === Foo.prototype,所以输出 3;
new new Foo().getName(), 这里等价于 new (new Foo().getName()),如上述 6,先输出 3,然后 new 一个 new Foo().getName() 的实例。
列表转成树形结构
题目描述:
[ { id: 1, text: '节点1', parentId: 0 //这里用0表示为顶级节点 }, { id: 2, text: '节点1_1', parentId: 1 //通过这个字段来确定子父级 } ...]
转成[ { id: 1, text: '节点1', parentId: 0, children: [ { id:2, text: '节点1_1', parentId:1 } ] }]
复制代码
实现代码如下:
function listToTree(data) { let temp = {}; let treeData = []; for (let i = 0; i < data.length; i++) { temp[data[i].id] = data[i]; } for (let i in temp) { if (+temp[i].parentId != 0) { if (!temp[temp[i].parentId].children) { temp[temp[i].parentId].children = []; } temp[temp[i].parentId].children.push(temp[i]); } else { treeData.push(temp[i]); } } return treeData;}
复制代码
对节流与防抖的理解
防抖函数的应用场景:
节流函数的适⽤场景:
参考 前端进阶面试题详细解答
instanceof
作用:判断对象的具体类型。可以区别 array 和 object, null 和 object 等。
语法:A instanceof B
如何判断的?: 如果 B 函数的显式原型对象在 A 对象的原型链上,返回true,否则返回false。
注意:如果检测原始值,则始终返回 false。
实现:
function myinstanceof(left, right) { // 基本数据类型都返回 false,注意 typeof 函数 返回"function" if((typeof left !== "object" && typeof left !== "function") || left === null) return false; let leftPro = left.__proto__; // 取左边的(隐式)原型 __proto__ // left.__proto__ 等价于 Object.getPrototypeOf(left) while(true) { // 判断是否到原型链顶端 if(leftPro === null) return false; // 判断右边的显式原型 prototype 对象是否在左边的原型链上 if(leftPro === right.prototype) return true; // 原型链查找 leftPro = leftPro.__proto__; }}
复制代码
了解 this 嘛,bind,call,apply 具体指什么
它们都是函数的方法
call: Array.prototype.call(this, args1, args2]) apply: Array.prototype.apply(this, [args1, args2]) :ES6 之前用来展开数组调用, foo.appy(null, []),ES6 之后使用 ... 操作符
四条规则:
function foo() { console.log(this.a); }
var a = 2;foo();
复制代码
function foo() { console.log(this.a);}
var obj = { a: 2, foo: foo,}
obj.foo(); // 2
复制代码
function foo() { console.log(this.a);}
var obj = { a: 2};
foo.call(obj);
复制代码
显示绑定之硬绑定
function foo(something) { console.log(this.a, something);
return this.a + something;}
function bind(fn, obj) { return function() { return fn.apply(obj, arguments); };}
var obj = { a: 2}
var bar = bind(foo, obj);
复制代码
New 绑定,new 调用函数会创建一个全新的对象,并将这个对象绑定到函数调用的 this。
function foo(a) { this.a = a;}
var bar = new foo(2);console.log(bar.a)
复制代码
Set,Map 解构
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。 Set 本身是一个构造函数,用来生成 Set 数据结构。
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
复制代码
代码输出结果
Promise.resolve(1) .then(2) .then(Promise.resolve(3)) .then(console.log)
复制代码
输出结果如下:
1Promise {<fulfilled>: undefined}
复制代码
Promise.resolve 方法的参数如果是一个原始值,或者是一个不具有 then 方法的对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为 resolved,Promise.resolve 方法的参数,会同时传给回调函数。
then 方法接受的参数是函数,而如果传递的并非是一个函数,它实际上会将其解释为 then(null),这就会导致前一个 Promise 的结果会传递下面。
如何防御 XSS 攻击?
可以看到 XSS 危害如此之大, 那么在开发网站时就要做好防御措施,具体措施如下:
CSP 指的是内容安全策略,它的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截由浏览器自己来实现。
通常有两种方式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的方式
Promise 是什么?
Promise 是异步编程的一种解决方案:从语法上讲,promise 是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。promise 有三种状态: pending(等待态),fulfiled(成功态),rejected(失败态) ;状态一旦改变,就不会再变。创造 promise 实例后,它会立即执行。
const PENDING = "pending";const RESOLVED = "resolved";const REJECTED = "rejected";
function MyPromise(fn) { // 保存初始化状态 var self = this;
// 初始化状态 this.state = PENDING;
// 用于保存 resolve 或者 rejected 传入的值 this.value = null;
// 用于保存 resolve 的回调函数 this.resolvedCallbacks = [];
// 用于保存 reject 的回调函数 this.rejectedCallbacks = [];
// 状态转变为 resolved 方法 function resolve(value) { // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变 if (value instanceof MyPromise) { return value.then(resolve, reject); }
// 保证代码的执行顺序为本轮事件循环的末尾 setTimeout(() => { // 只有状态为 pending 时才能转变, if (self.state === PENDING) { // 修改状态 self.state = RESOLVED;
// 设置传入的值 self.value = value;
// 执行回调函数 self.resolvedCallbacks.forEach(callback => { callback(value); }); } }, 0); }
// 状态转变为 rejected 方法 function reject(value) { // 保证代码的执行顺序为本轮事件循环的末尾 setTimeout(() => { // 只有状态为 pending 时才能转变 if (self.state === PENDING) { // 修改状态 self.state = REJECTED;
// 设置传入的值 self.value = value;
// 执行回调函数 self.rejectedCallbacks.forEach(callback => { callback(value); }); } }, 0); }
// 将两个方法传入函数执行 try { fn(resolve, reject); } catch (e) { // 遇到错误时,捕获错误,执行 reject 函数 reject(e); }}
MyPromise.prototype.then = function(onResolved, onRejected) { // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数 onResolved = typeof onResolved === "function" ? onResolved : function(value) { return value; };
onRejected = typeof onRejected === "function" ? onRejected : function(error) { throw error; };
// 如果是等待状态,则将函数加入对应列表中 if (this.state === PENDING) { this.resolvedCallbacks.push(onResolved); this.rejectedCallbacks.push(onRejected); }
// 如果状态已经凝固,则直接执行对应状态的函数
if (this.state === RESOLVED) { onResolved(this.value); }
if (this.state === REJECTED) { onRejected(this.value); }};
复制代码
二分查找--时间复杂度 log2(n)
题目描述:如何确定一个数在一个有序数组中的位置
实现代码如下:
function search(arr, target, start, end) { let targetIndex = -1;
let mid = Math.floor((start + end) / 2);
if (arr[mid] === target) { targetIndex = mid; return targetIndex; }
if (start >= end) { return targetIndex; }
if (arr[mid] < target) { return search(arr, target, mid + 1, end); } else { return search(arr, target, start, mid - 1); }}// const dataArr = [1, 2, 3, 4, 5, 6, 7, 8, 9];// const position = search(dataArr, 6, 0, dataArr.length - 1);// if (position !== -1) {// console.log(`目标元素在数组中的位置:${position}`);// } else {// console.log("目标元素不在数组中");// }
复制代码
事件委托的使用场景
场景:给页面的所有的 a 标签添加 click 事件,代码如下:
document.addEventListener("click", function(e) { if (e.target.nodeName == "A") console.log("a");}, false);
复制代码
但是这些 a 标签可能包含一些像 span、img 等元素,如果点击到了这些 a 标签中的元素,就不会触发 click 事件,因为事件绑定上在 a 标签元素上,而触发这些内部的元素时,e.target 指向的是触发 click 事件的元素(span、img 等其他元素)。
这种情况下就可以使用事件委托来处理,将事件绑定在 a 标签的内部元素上,当点击它的时候,就会逐级向上查找,知道找到 a 标签为止,代码如下:
document.addEventListener("click", function(e) { var node = e.target; while (node.parentNode.nodeName != "BODY") { if (node.nodeName == "A") { console.log("a"); break; } node = node.parentNode; }}, false);
复制代码
说一下怎么取出数组最多的一项?
// 我这里只是一个示例const d = {};let ary = ['赵', '钱', '孙', '孙', '李', '周', '李', '周', '周', '李'];ary.forEach(k => !d[k] ? d[k] = 1 : d[k]++);const result = Object.keys(d).sort((a, b) => d[b] - d[a]).filter((k, i, l) => d[k] === d[l[0]]);console.log(result)
复制代码
什么是执行栈
可以把执行栈认为是一个存储函数调用的栈结构,遵循先进后出的原则。 当开始执行 JS 代码时,根据先进后出的原则,后执行的函数会先弹出栈,可以看到,foo 函数后执行,当执行完毕后就从栈中弹出了。
平时在开发中,可以在报错中找到执行栈的痕迹:
function foo() { throw new Error('error')}function bar() { foo()}bar()
复制代码
可以看到报错在 foo 函数,foo 函数又是在 bar 函数中调用的。当使用递归时,因为栈可存放的函数是有限制的,一旦存放了过多的函数且没有得到释放的话,就会出现爆栈的问题
function bar() { bar()}bar()
复制代码
深拷贝
实现一:不考虑 Symbol
function deepClone(obj) { if(!isObject(obj)) return obj; let newObj = Array.isArray(obj) ? [] : {}; // for...in 只会遍历对象自身的和继承的可枚举的属性(不含 Symbol 属性) for(let key in obj) { // obj.hasOwnProperty() 方法只考虑对象自身的属性 if(obj.hasOwnProperty(key)) { newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]; } } return newObj;}
复制代码
实现二:考虑 Symbol
// hash 作为一个检查器,避免对象深拷贝中出现环引用,导致爆栈function deepClone(obj, hash = new WeakMap()) { if(!isObject(obj)) return obj; // 检查是有存在相同的对象在之前拷贝过,有则返回之前拷贝后存于hash中的对象 if(hash.has(obj)) return hash.get(obj); let newObj = Array.isArray(obj) ? [] : {}; // 备份存在hash中,newObj目前是空对象、数组。后面会对属性进行追加,这里存的值是对象的栈 hash.set(obj, newObj); // Reflect.ownKeys返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。 Reflect.ownKeys(obj).forEach(key => { // 属性值如果是对象,则进行递归深拷贝,否则直接拷贝 newObj[key] = isObject(obj[key]) ? deepClone(obj[key], hash) : obj[key]; }); return newObj;}
复制代码
手写题:Promise 原理
class MyPromise { constructor(fn) { this.callbacks = []; this.state = "PENDING"; this.value = null;
fn(this._resolve.bind(this), this._reject.bind(this)); }
then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => this._handle({ onFulfilled: onFulfilled || null, onRejected: onRejected || null, resolve, reject, }) ); }
catch(onRejected) { return this.then(null, onRejected); }
_handle(callback) { if (this.state === "PENDING") { this.callbacks.push(callback);
return; }
let cb = this.state === "FULFILLED" ? callback.onFulfilled : callback.onRejected; if (!cb) { cb = this.state === "FULFILLED" ? callback.resolve : callback.reject; cb(this.value);
return; }
let ret;
try { ret = cb(this.value); cb = this.state === "FULFILLED" ? callback.resolve : callback.reject; } catch (error) { ret = error; cb = callback.reject; } finally { cb(ret); } }
_resolve(value) { if (value && (typeof value === "object" || typeof value === "function")) { let then = value.then;
if (typeof then === "function") { then.call(value, this._resolve.bind(this), this._reject.bind(this));
return; } }
this.state === "FULFILLED"; this.value = value; this.callbacks.forEach((fn) => this._handle(fn)); }
_reject(error) { this.state === "REJECTED"; this.value = error; this.callbacks.forEach((fn) => this._handle(fn)); }}
const p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error("fail")), 3000);});
const p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000);});
p2.then((result) => console.log(result)).catch((error) => console.log(error));
复制代码
数据类型判断
核心思想:typeof 可以判断 Undefined、String、Number、Boolean、Symbol、Function类型的数据,但对其他的都会认为是Object,比如Null、Array等。所以通过typeof来判断数据类型会不准确。
解决方法:可以通过Object.prototype.toString解决。
实现:
function mytypeof(obj) { return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();}
复制代码
使用call 是为了绑定 this 到 obj 上
使用slice 是因为这前面返回的结果是类似[Object xxx]这样的, xxx 是根据 obj 的类型变化的
使用toLowerCase 是因为原生typeof的返回结果的第一个字母是小写字母。
写版本号排序的方法
题目描述:有一组版本号如下['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。现在需要对其进行排序,排序的结果为 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']
实现代码如下:
arr.sort((a, b) => { let i = 0; const arr1 = a.split("."); const arr2 = b.split(".");
while (true) { const s1 = arr1[i]; const s2 = arr2[i]; i++; if (s1 === undefined || s2 === undefined) { return arr2.length - arr1.length; }
if (s1 === s2) continue;
return s2 - s1; }});console.log(arr);
复制代码
评论