代码输出结果
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、 Module
1、state => 基本数据(数据源存放地)
2、getters => 从基本数据派生出来的数据
3、mutations => 提交更改数据的方法,同步
4、actions => 像一个装饰器,包裹mutations,使之可以异步。
5、modules => 模块化Vuex
1、Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过 Action 来提交 mutation实现,这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用。
2、每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。
复制代码
代码输出结果
// a
function Foo () {
getName = function () {
console.log(1);
}
return this;
}
// b
Foo.getName = function () {
console.log(2);
}
// c
Foo.prototype.getName = function () {
console.log(3);
}
// d
var getName = function () {
console.log(4);
}
// e
function getName () {
console.log(5);
}
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new 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)
复制代码
输出结果如下:
1
Promise {<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);
复制代码
评论