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
- 指数函数的中缀表达式,
**
表示Math.pow()
- Array.prototype.includes,快速查找数组中是否包含某个元素,包括
NaN
。
2.2.2 ES 8
- Object.values(obj)/Object.entries(obj),
Object
上的静态方法,遍历出对象的value
/{key: value}
。 - String.prototype.padStart(str.length, '填充字符串'),实例方法,从字符串前面开始填充传入的第二个参数,直至长度变成传入的第一个参数。
- String.prototype.padEnd(str.length, '填充字符串'),字符串追加填充,参数同上
- Object.getOwnPropertyDescriptors(obj),获取对象的所有自有的属性的属性描述对象。
- 结尾允许逗号
2.2.3 ES 9
- Promise.prototype.finally(callback),
Promise
无论如何都会执行finally
的回调。 - rest支持Object,例如:{...obj}
2.2.4 ES 10
- 可选的catch变量,即,
catch(e){}
如果不需要变量e
可简写为catch {}
- JSON超集
- Symbol的description属性,为
Symbol
添加description
属性,能够直接访问description
。 - 修订Function.prototype.toString,函数实例的
toString
方法现在反悔精确字符,包括空格和注释。 - Object.fromEntires(),可以把键值对类型的数据结构转为一个对象。
- String.trimStart/trimEnd(),去除字符串前或后的空格
- Array.prototype.flat(times),将数组拉平,times是层数,默认是一层。
- Array.prototype.flatMap(),先对数组执行了
map
方法,然后再执行拉平
2.2.5 ES 11
- import()函数,根据传参动态懒加载
- Promise.allSettled(),和
Promise.all
一样使用,不过不具有一票否决制了,可以拿到里面素有promise
的结果。
2.3 函数
2.3.1 匿名函数如何调用自身实现递归?
因为函数内的arguments.callee
属性指向函数自身,所以可以通过arguments.callee()
来调用自身。
2.3.2 箭头函数有什么特点?
- 箭头函数内不暴露
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
- 因为变量提升会让人难以发现错误;
- 因为
var
可以重复声明变量,导致后面的覆盖前面的; -
var
会造成变量污染
所以除了定义全局变量外。尽量避免使用var
2.5 执行上下文
执行上下文就是当前JavaScript
代码被解析和执行时所在的环境,故也称之为执行环境。
2.5.1 执行上下文分为哪几种?
- 全局执行上下文,不在任何函数中的代码都位于全局执行上下文中。它做了两件事:1. 创建了一个全局对象,在浏览器中这个全局对象就是
window
,2. 将this
指针指向这个全局对象,一个程序中只能存在一个全局执行上下文。 - 函数执行上下文,每次调用函数时,都会为该函数创建一个新的执行上下文,每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。
- eval执行上下文。
2.5.2 执行上下文的生命周期
创建阶段 → 执行阶段 → 回收阶段
2.5.3 请简述执行上下文的创建阶段都做了什么?
这里只说函数执行上下文。
- 创建变量对象:首先初始化函数的参数
arguments
,提升函数声明和变量声明。 - 创建作用域链:作用于链是在变量对象之后创建的。确定
outer
的指向。 - 确定
this
的指向。this
指向调用该函数的对象。 - 压入执行栈。
2.6 this相关
2.6.1 为什么要有this?
JavaScript
允许在函数体内部引用当前执行上下文的其他变量,所以JavaScript
需要一种机制可以依靠其优雅,准确的指向当代码运行时的上下文环境。
2.6.2 new操作符原理
- 创造一个空对象
- 让空对象的原型指向构造函数的原型
- 将构造函数的
this
指向空对象然后执行构造函数 - 判断构造函数是否返回非空对象,如果是则返回构造函数的返回内容,否则就返回新生成的对象。
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的应用场景
- 保存函数的参数,在声明函数的时候就可以绑定参数。
- 保存
this
,声明函数时可以绑定this
,当然也可以用箭头函数
2.6.5 实现call
思路:
- 参考call的语法规则,需要设置一个参数thisArg,也就是this的指向;
- 将thisArg封装为一个Object;
- 通过为thisArg创建一个临时方法,这样thisArg就是调用该临时方法的对象了,会将该临时方法的this隐式指向到thisArg上
- 执行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;//返回拷贝的函数
};
网友评论