2018年五月面试发现的一些问题
最近回来一直在面试,除开周末,四天大概面试了十多家公司,在面试过程中也发现了自己的一些知识漏洞,现在整理一下。
关于详细的面试记录整理,我放在github上了,这篇博客主要整理我之前遗漏的一些问题。其中大部分是ES6中一些用的比较少的特性
proxy
这个问题是在问Vue的数据代理实现原理后,询问是否了解ES6的代理 参考MDN文档。
Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。
可以理解为是对对象的进行操作时的一个拦截器。跟defineproperty
有点类似
let p = new Proxy(target, handler);
对对象常见的操作有set
、get
等,都可通过handler对应的属性进行配置
{
set: function(obj, prop, newval) {},
get: function(obj, prop) {},
deleteProperty: function (oTarget, sKey) {},
enumerate: function (oTarget, sKey) {},
ownKeys: function (oTarget, sKey) {},
has: function (oTarget, sKey) {},
defineProperty: function (oTarget, sKey, oDesc) {},
getOwnPropertyDescriptor: function (oTarget, sKey) {}
}
这里有一篇关于proxy的应用,实现双向绑定Proxy比defineproperty优劣如何?,不妨移步阅读。
symbol
这个问题是一道回答JS基础数据类型的笔试题遇见的,当时漏掉了symbol。
之前看MDN文档的时候对于symbol的概念一直不清晰,直到了解到他的作用
一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。每个从Symbol()返回的symbol值都是唯一的。
为什么存在这个需求呢?当多人合作编码的时候,经常会出现你往对象上加了一个某某属性(比如 $ ),他人正好也想到了这个名称,当你们同时用了这个名称作为属性,代码之间就会发生冲突,互相覆盖。而用 symbol,即使都用了相同的描述,也不是同一个 symbol。
通过Symbol()
全局函数获得一个symbol数据,下面是symbol的使用方法
let sy1 = Symbol('foo')
let sy2 = Symbol('foo')
let obj = {
[sy1]: 123,
[sy2]: 456
}
console.log(sy1 == sy2) // false
console.log(sy1 === sy2) // false
console.log(obj[sy1]) // 123
console.log(obj[sy2]) // 456
注意Symbol不能通过new
操作符进行函数的构造调用,这是因为
围绕原始数据类型创建一个显式包装器对象从 ECMAScript 6 开始不再被支持,旧版String、Boolean、Number等由于兼容仍旧保留
迭代器和生成器
这个问题好像是问是否了解迭代器。
迭代器和生成器的区别在于
- 一个迭代器对象 ,知道如何每次访问集合中的一项(
next()
接口), 并跟踪该序列中的当前位置。 - 生成器Generators提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数, 同时它可以自动维护自己的状态。
可以使用for...of
用来遍历一个迭代器对象,常见的诸如Array、String、Map、Set、arguments、DOM集合以及其他自定义迭代器。
协议
迭代器协议
当一个对象被认为是一个迭代器时,它实现了一个 next() 的方法,next返回value和done状态
可迭代协议
为了变成可迭代对象, 一个对象必须实现 @@iterator 方法, 意思是这个对象(或者它原型链 prototype chain 上的某个对象)必须有一个名字是 Symbol.iterator 的属性:
Foo.prototype[Symbol.iterator] = function (params) {
return {
next(){
let value, done
return {
value,
done
}
}
}
}
生成器函数
参考:
生成器函数在执行时能暂停,后面又能从暂停处继续执行。 调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器 (iterator )对象
function* idMaker() {
var index = 0;
while (index < 3) {
yield index++
}
return 'this is finished'
}
var gen = idMaker();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // this is finished
console.log(gen.next().value); // undefined 状态变成done则一直返回undefined
console.log(gen.next().value); // undefined
由于生成器函数返回的是一个迭代器对象,因此上面对于迭代器的操作均适用于生成器函数的返回值
// 注意这里没有return返回的结果
console.log([...gen]) // 1, 2, 3
生成器函数在JavaScript的异步编程中十分有用,之前看见一句话
异步编程的语法目标,就是怎样让它更像同步编程
这跟生成器函数的调用十分相似,由于Generator 函数可以暂停执行和恢复执行,试想如果在异步时暂停函数执行,在异步任务结束后中手动去调用next恢复代码执行,是不是就可以通过同步代码去实现异步编程呢?
function fetch(){
return new Promise((reslove, reject)=>{
setTimeout(() => {
let data = 'hello'
reslove(data)
}, 1000);
})
}
function * getAsyncData(params) {
console.log('start fetch')
let result = yield fetch()
console.log(result)
}
let gen = getAsyncData()
let result = gen.next();
result.value.then(function (data) {
return data;
}).then(function (data) {
gen.next(data);
这只是我大致的思路,关于generator函数,后面会单独整理一篇博客。
CMD与AMD有啥区别
这个问题是询问AMD
和CommonJS
的区别时引申出来的,听见这个问题脑袋突然短路了,才发现这个好像是个知识盲区。 一直以来觉得模块化就是AMD、CommonJS和ES6模块这些,丢脸了~
之前用过一段时间的requireJS
,是AMD规范的实现;而seaJS
,则是CMD的实现。他们最主要的区别在于
SeaJS对模块的态度是懒执行, 而RequireJS对模块的态度是预执行
换句话说,
- SeaJS只会在真正需要使用(依赖)模块时才执行该模块,因此模块的加载顺序跟代码执行顺保持一致
- RequireJS会先尽早地执行(依赖)模块, 相当于所有的require都被提前了,模块的解析顺序是不能保证的
当然在浏览器中,模块的加载顺序都异步的,且模块的依赖关系肯定是可以确定的。
参考:
小结
上面只是简单整理了几个印象比较深刻的知识盲点,更详细的面试记录都放在那个git仓库里面了。这次回来花了一周时间,大概面试了十多家公司,拿到了七八个offer,现在对于简历和面试这些倒是不太畏惧了。
但是遇见“你评价一下自己”、“你觉得自己有什么优缺点”之类的,我一向不擅长回答这种问题,不过就从业经验和团队协作这方面来看,我肯定是欠缺的,如果加入一个比较大的团队,可能需要更多的时间来适应~
所以,还是要继续努力,现在入职两周多,工作流程、业务啥的都需要适应。会当凌绝顶,一览众山小!
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。