面试整理-Javascript(上)

2020-05-16  本文已影响0人  前端艾希

1. 你还有什么要问我的?

2. 面试题

2.1 为Object提供iterator接口

let a = {
    name: 'bing',
    age: 25
}

let gen = function * iterator () {
    let keys = Object.keys(this)
    let i = 0
    while (i < keys.length) {
        yield this[keys[i]]
        i += 1
    }
    return
}

a[Symbol.iterator] = gen

// 测试
for (let val of a) {
    console.log(val)
}
// bing
// 25

2.2 ES 6 以后的新特性

2.2.1 ES 7

2.2.2 ES 8

2.2.3 ES 9

2.2.4 ES 10

2.2.5 ES 11

2.3 函数

2.3.1 匿名函数如何调用自身实现递归?

因为函数内的arguments.callee属性指向函数自身,所以可以通过arguments.callee()来调用自身。

2.3.2 箭头函数有什么特点?

  1. 箭头函数内不暴露arguments对象;2. 箭头函数没有自己的super或者new.target

2.3.3 什么是高阶函数?

如果某个函数可以接受另一个函数作为参数,该函数就称之为高阶函数。最常见的就是回调函数作为参数传递。

2.3.4 什么是函数的重载?

重载就是函数名一样,但是随着传入的参数个数不一样,调用的逻辑或者返回的结果不一样。

2.4 作用域

2.4.1 什么是作用域?

作用域即函数或者变量的可见区域,在除该区域外的任何区域无法被访问。

2.4.2 ES 6以后作用域分为哪几种?

全局作用域和局部作用域(函数作用域,块级作用域)

2.4.3 在块级作用域中声明函数需要注意什么?

为了保持向下兼容,在{}中声明的函数仍然能够在外部被访问到,如果想要达到预期效果,我们应当使用let来定义一个变量来接收匿名函数,这样定义的函数在代码块外部就不能被访问了。

2.4.4 为什么要用let而不用var

  1. 因为变量提升会让人难以发现错误;
  2. 因为var可以重复声明变量,导致后面的覆盖前面的;
  3. var会造成变量污染

所以除了定义全局变量外。尽量避免使用var

2.5 执行上下文

执行上下文就是当前JavaScript代码被解析和执行时所在的环境,故也称之为执行环境。

2.5.1 执行上下文分为哪几种?

2.5.2 执行上下文的生命周期

创建阶段 → 执行阶段 → 回收阶段

2.5.3 请简述执行上下文的创建阶段都做了什么?

这里只说函数执行上下文。

  1. 创建变量对象:首先初始化函数的参数arguments,提升函数声明和变量声明。
  2. 创建作用域链:作用于链是在变量对象之后创建的。确定outer的指向。
  3. 确定this的指向。this指向调用该函数的对象。
  4. 压入执行栈。

2.6 this相关

2.6.1 为什么要有this?

JavaScript允许在函数体内部引用当前执行上下文的其他变量,所以JavaScript需要一种机制可以依靠其优雅,准确的指向当代码运行时的上下文环境。

2.6.2 new操作符原理

  1. 创造一个空对象
  2. 让空对象的原型指向构造函数的原型
  3. 将构造函数的this指向空对象然后执行构造函数
  4. 判断构造函数是否返回非空对象,如果是则返回构造函数的返回内容,否则就返回新生成的对象。
function New(constructor, ...args){
    const _obj = {}
    _obj.__proto__ = constructor.prototype
    _obj.constructor = constructor
    const retu = constructor.call(_obj, ...args)
    return typeof retu === 'object' && retu !== null ? retu : _obj
}

2.6.3 为什么要使用call,apply,bind?

这三者的核心概念是借用,即借助已实现的方法,改变方法中this的指向减少重复代码,节省内存。

2.6.4 bind的应用场景

  1. 保存函数的参数,在声明函数的时候就可以绑定参数。
  2. 保存this,声明函数时可以绑定this,当然也可以用箭头函数

2.6.5 实现call

思路:

  1. 参考call的语法规则,需要设置一个参数thisArg,也就是this的指向;
  2. 将thisArg封装为一个Object;
  3. 通过为thisArg创建一个临时方法,这样thisArg就是调用该临时方法的对象了,会将该临时方法的this隐式指向到thisArg上
  4. 执行thisArg的临时方法,并传递参数;
    删除临时方法,返回方法的执行结果。
/**
 * 用原生JavaScript实现call
 */
Function.prototype.myCall = function(thisArg, ...arr) {

  //1.判断参数合法性/////////////////////////
  if (thisArg === null || thisArg === undefined) {
    //指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
    thisArg = window;
  } else {
    thisArg = Object(thisArg);//创建一个可包含数字/字符串/布尔值的对象,
                              //thisArg 会指向一个包含该原始值的对象。
  }

  //2.搞定this的指向/////////////////////////
  const specialMethod = Symbol("anything"); //创建一个不重复的常量
  //如果调用myCall的函数名是func,也即以func.myCall()形式调用;
  //根据上篇文章介绍,则myCall函数体内的this指向func
  thisArg[specialMethod] = this; //给thisArg对象建一个临时属性来储存this(也即func函数)
  //进一步地func作为thisArg对象的一个方法被调用,那么func中的this便
  //指向thisArg对象。由此,巧妙地完成将this隐式地指向到thisArg!
  let result = thisArg[specialMethod](...arr);
  
  //3.收尾
  delete thisArg[specialMethod]; //删除临时方法
  return result; //返回临时方法的执行结果
};

let obj = {
  name: "coffe1891"
};

function func() {
  console.log(this.name);
}

func.myCall(obj);//>> coffe1891

2.6.6 实现一个apply

/**
 * 用原生JavaScript实现apply
 */
Function.prototype.myApply = function(thisArg) {
  if (thisArg === null || thisArg === undefined) {
    thisArg = window;
  } else {
    thisArg = Object(thisArg);
  }

  //判断是否为【类数组对象】
  function isArrayLike(o) {
    if (
      o && // o不是null、undefined等
      typeof o === "object" && // o是对象
      isFinite(o.length) && // o.length是有限数值
      o.length >= 0 && // o.length为非负值
      o.length === Math.floor(o.length) && // o.length是整数
      o.length < 4294967296
    )
      // o.length < 2^32
      return true;
    else return false;
  }

  const specialMethod = Symbol("anything");
  thisArg[specialMethod] = this;

  let args = arguments[1]; // 获取参数数组
  let result;

  // 处理传进来的第二个参数
  if (args) {
    // 是否传递第二个参数
    if (!Array.isArray(args) && !isArrayLike(args)) {
      throw new TypeError(
        "第二个参数既不为数组,也不为类数组对象。抛出错误"
      );
    } else {
      args = Array.from(args); // 转为数组
      result = thisArg[specialMethod](...args); // 执行函数并展开数组,传递函数参数
    }
  } else {
    result = thisArg[specialMethod]();
  }

  delete thisArg[specialMethod];
  return result; // 返回函数执行结果
};

2.6.7 实现bind

/**
 * 用原生JavaScript实现bind
 */
Function.prototype.myBind = function(objThis, ...params) {
  const thisFn = this;//存储调用函数,以及上方的params(函数参数)
  //对返回的函数 secondParams 二次传参
  let funcForBind = function(...secondParams) {
    //检查this是否是funcForBind的实例?也就是检查funcForBind是否通过new调用
    const isNew = this instanceof funcForBind;

    //new调用就绑定到this上,否则就绑定到传入的objThis上
    const thisArg = isNew ? this : Object(objThis);

    //用call执行调用函数,绑定this的指向,并传递参数。返回执行结果
    return thisFn.call(thisArg, ...params, ...secondParams);
  };

  //复制调用函数的prototype给funcForBind
  funcForBind.prototype = Object.create(thisFn.prototype);
  return funcForBind;//返回拷贝的函数
};

参考链接

前端内参

上一篇 下一篇

猜你喜欢

热点阅读