关于javascript:那些高级前端是如何回答面试题的

54次阅读

共计 11436 个字符,预计需要花费 29 分钟才能阅读完成。

代码输入后果

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))

输入后果如下:

1
'result:' 1
2
3

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

解析:

  1. Foo.getName(), Foo 为一个函数对象,对象都能够有属性,b 处定义 Foo 的 getName 属性为函数,输入 2;
  2. getName(), 这里看 d、e 处,d 为函数表达式,e 为函数申明,两者区别在于变量晋升,函数申明的 5 会被后边函数表达式的 4 笼罩;
  3. Foo().getName(), 这里要看 a 处,在 Foo 外部将全局的 getName 从新赋值为 console.log(1) 的函数,执行 Foo()返回 this,这个 this 指向 window,Foo().getName() 即为 window.getName(),输入 1;
  4. getName(), 下面 3 中,全局的 getName 曾经被从新赋值,所以这里仍然输入 1;
  5. new Foo.getName(), 这里等价于 new (Foo.getName()),先执行 Foo.getName(),输入 2,而后 new 一个实例;
  6. new Foo().getName(), 这 里等价于 (new Foo()).getName(), 先 new 一个 Foo 的实例,再执行这个实例的 getName 办法,然而这个实例自身没有这个办法,所以去原型链__protot__上边找,实例.protot === Foo.prototype,所以输入 3;
  7. 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;
}

对节流与防抖的了解

  • 函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则从新计时。这能够应用在一些点击申请的事件上,防止因为用户的屡次点击向后端发送屡次申请。
  • 函数节流是指规定一个单位工夫,在这个单位工夫内,只能有一次触发事件的回调函数执行,如果在同一个单位工夫内某事件被触发屡次,只有一次能失效。节流能够应用在 scroll 函数的事件监听上,通过事件节流来升高事件调用的频率。

防抖函数的利用场景:

  • 按钮提交场景:防⽌屡次提交按钮,只执⾏最初提交的⼀次
  • 服务端验证场景:表单验证须要服务端配合,只执⾏⼀段间断的输⼊事件的最初⼀次,还有搜寻联想词性能相似⽣存环境请⽤ lodash.debounce

节流函数的适⽤场景:

  • 拖拽场景:固定工夫内只执⾏⼀次,防⽌超⾼频次触发地位变动
  • 缩放场景:监控浏览器 resize
  • 动画场景:防止短时间内屡次触发动画引起性能问题

参考 前端进阶面试题具体解答

instanceof

作用:判断对象的具体类型。能够区别 arrayobjectnullobject 等。

语法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 之后应用 … 操作符

  • New 绑定 > 显示绑定 > 隐式绑定 > 默认绑定
  • 如果须要应用 bind 的柯里化和 apply 的数组解构,绑定到 null,尽可能应用 Object.create(null) 创立一个 DMZ 对象

四条规定:

  • 默认绑定,没有其余润饰(bind、apply、call),在非严格模式下定义指向全局对象,在严格模式下定义指向 undefined
function foo() {console.log(this.a); 
}

var a = 2;
foo();
  • 隐式绑定:调用地位是否有上下文对象,或者是否被某个对象领有或者蕴含,那么隐式绑定规定会把函数调用中的 this 绑定到这个上下文对象。而且,对象属性链只有上一层或者说最初一层在调用地位中起作用
function foo() {console.log(this.a);
}

var obj = {
  a: 2,
  foo: foo,
}

obj.foo(); // 2
  • 显示绑定:通过在函数上运行 call 和 apply,来显示的绑定 this
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。

  • New 绑定时,如果是 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 危害如此之大,那么在开发网站时就要做好进攻措施,具体措施如下:

  • 能够从浏览器的执行来进行预防,一种是应用纯前端的形式,不必服务器端拼接后返回(不应用服务端渲染)。另一种是对须要插入到 HTML 中的代码做好充沛的本义。对于 DOM 型的攻打,次要是前端脚本的不牢靠而造成的,对于数据获取渲染和字符串拼接的时候应该对可能呈现的恶意代码状况进行判断。
  • 应用 CSP,CSP 的实质是建设一个白名单,通知浏览器哪些内部资源能够加载和执行,从而避免恶意代码的注入攻打。
  1. CSP 指的是内容安全策略,它的实质是建设一个白名单,通知浏览器哪些内部资源能够加载和执行。咱们只须要配置规定,如何拦挡由浏览器本人来实现。
  2. 通常有两种形式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的形式
  • 对一些敏感信息进行爱护,比方 cookie 应用 http-only,使得脚本无奈获取。也能够应用验证码,防止脚本伪装成用户执行一些操作。

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();}
  1. 应用call 是为了绑定 thisobj
  2. 应用 slice 是因为这后面返回的后果是相似[Object xxx] 这样的, xxx 是依据 obj 的类型变动的
  3. 应用 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);

正文完
 0