2018年五月面试发现的一些问题

最近回来一直在面试,除开周末,四天大概面试了十多家公司,在面试过程中也发现了自己的一些知识漏洞,现在整理一下。

<!--more-->

关于详细的面试记录整理,我放在github上了,这篇博客主要整理我之前遗漏的一些问题。其中大部分是ES6中一些用的比较少的特性

1. proxy

这个问题是在问Vue的数据代理实现原理后,询问是否了解ES6的代理 参考MDN文档

Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

可以理解为是对对象的进行操作时的一个拦截器。跟defineproperty有点类似

let p = new Proxy(target, handler);

对对象常见的操作有setget等,都可通过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优劣如何?,不妨移步阅读。

2. 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等由于兼容仍旧保留

3. 迭代器和生成器

这个问题好像是问是否了解迭代器。

迭代器和生成器的区别在于

  • 一个迭代器对象 ,知道如何每次访问集合中的一项(next()接口), 并跟踪该序列中的当前位置。
  • 生成器Generators提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数, 同时它可以自动维护自己的状态。

可以使用for...of用来遍历一个迭代器对象,常见的诸如Array、String、Map、Set、arguments、DOM集合以及其他自定义迭代器。

3.1. 协议

迭代器协议

当一个对象被认为是一个迭代器时,它实现了一个 next() 的方法,next返回value和done状态

可迭代协议

为了变成可迭代对象, 一个对象必须实现 @@iterator 方法, 意思是这个对象(或者它原型链 prototype chain 上的某个对象)必须有一个名字是 Symbol.iterator 的属性:

Foo.prototype[Symbol.iterator] = function (params) {
    return {
        next(){
            let value, done
            return {
                value,
                done
            }
        }
    }
}

3.2. 生成器函数

参考:

生成器函数在执行时能暂停,后面又能从暂停处继续执行。 调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器 (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函数,后面会单独整理一篇博客。

4. CMD与AMD有啥区别

这个问题是询问AMDCommonJS的区别时引申出来的,听见这个问题脑袋突然短路了,才发现这个好像是个知识盲区。 一直以来觉得模块化就是AMD、CommonJS和ES6模块这些,丢脸了~

之前用过一段时间的requireJS,是AMD规范的实现;而seaJS,则是CMD的实现。他们最主要的区别在于

SeaJS对模块的态度是懒执行, 而RequireJS对模块的态度是预执行

换句话说,

  • SeaJS只会在真正需要使用(依赖)模块时才执行该模块,因此模块的加载顺序跟代码执行顺保持一致
  • RequireJS会先尽早地执行(依赖)模块, 相当于所有的require都被提前了,模块的解析顺序是不能保证的

当然在浏览器中,模块的加载顺序都异步的,且模块的依赖关系肯定是可以确定的。

参考:

5. 小结

上面只是简单整理了几个印象比较深刻的知识盲点,更详细的面试记录都放在那个git仓库里面了。这次回来花了一周时间,大概面试了十多家公司,拿到了七八个offer,现在对于简历和面试这些倒是不太畏惧了。

但是遇见“你评价一下自己”、“你觉得自己有什么优缺点”之类的,我一向不擅长回答这种问题,不过就从业经验和团队协作这方面来看,我肯定是欠缺的,如果加入一个比较大的团队,可能需要更多的时间来适应~

所以,还是要继续努力,现在入职两周多,工作流程、业务啥的都需要适应。会当凌绝顶,一览众山小!