阅读 14137

杭州17届前端期待加入一个更好的团队

最近看到有人分享16届前端进阿里(前端学习群的群主),17届前端杭州收割offer(优秀的路人)。 刚好同为17届的我最近也很轻松拿了2个offer,但是我看到他们写的题目回答并不是很详细,所以强迫症的我也想来和大家分享下,我会把所有没挂掉的和挂掉的都分享出来,遇到的每一道题目都做详细的分析,所以文章有点长哦。

长文慎入哈,但是有酒有故事,小板凳我都给你搬好啦,快坐下!

故事开始咯~

本来想等过完年回来再换工作的,但是前段时间有猎头在简书私信我,说看我的文章写得还行,问我想不想换工作。 然后我就更新了简历,发过去了,邀请我面试,但是这家公司在北京 /西安,后来就没去,但是故事就此开始了,这反而促使我走上了换工作的道路。

贴一下自认为写得不错的几篇文章:

CSS3动画卡顿性能优化解决方案

深度剖析0.1 +0.2===0.30000000000000004的原因

如何解决0.1 +0.2===0.30000000000000004类问题

记一次简单的DOM XSS攻击实验

你真的理解==和===的区别吗?

经典CSS坑:如何完美实现垂直水平居中?

自己从大三暑假实习到现在工作半年多,接近快1.5+年的时间,一门心思扎到前端开发领域,高强度式的工作和学习,买书啃书,写代码看代码,写博客看博客,提问题回答问题,投入了几乎是大一到大三学习时间总和的时间学习前端开发。

最近阴差阳错开始笔试,面试,虽然都没怎么准备,但是很轻松就收获了一家阿里子公司,一家浙大系公司的offer,再加上昨天被第三家面试官的赞赏,之前的妄自菲薄一下烟消云散。

由于自己这次换工作,希望能够在一家公司呆至少2年以上,非常希望能和一帮对前端技术有见解有思考有追求的小伙伴们,一起去探索钻研有趣的前端世界。所以我决定尝试更多的面试,加入到一个更适合自己的团队。

同时也希望通过这篇面试分享,能够给和我一样努力的前端小伙伴们一些鼓励,也希望能够把最真实的自己展现给大家。

面试1 - offer1 天道酬勤,付出终有回报

拒绝了第一家主动找到我的公司后,又有家阿里子创业公司在 BOSS 直聘里私信我,然后抱着测试水平的心态面了。 现场笔试不怎么样,但是他们看到我简书文章还算可以,就让我重做了一遍题目: 一些特别棒的面试题[0], 最后一道单词与字符串匹配的题的拓展,都是面试官一步步引导我去深入的,感觉学习到很多。

没想到结果是比较让人惊喜的,前辈说我的学习能力比较强,所以愿意给我这个机会,给的薪资比现在高 2K,关键是听说有许多项目可以做,据说面试我的老板以前也是在阿里的技术专家。

贴一下面试题和我的回答。

1.说一下你熟悉的设计模式
2.说一下你理解的模块机制
3.MVVM原理
4.最熟悉的框架路由机制
5.状态管理
6.统计字符串中单词出现次数
复制代码

1.说一下你熟悉的设计模式

我最熟悉的设计模式:工厂模式(ES5),组件设计模式(ES6) 工厂模式(ES5,基于prototype。此例中基类Base,子类Factory)

var Factory = function () {
    if(!(this instanceof Factory)){
        return new Factory();
    }
}
Factory.prototype = Object.assign(new Base(), {
    version: '0.0.1',
    defaultOption:{
        title:'标题'
    },
    init:function (cfg) {
        this.title = cfg.title || '';
        this.currentOption = Object.assign(this.defaultOption,{
           //...
        })
    },
    render: function () {
        var option = this.currentOption;
        this.chart.setOption(option);
    },
    showTitle: function () {
        this._showTitle();
    }
})
复制代码

组件设计模式(ES6,基于class,方便继承和初始化,也是React组件的推荐写法,我比较喜欢。此例中父类Compnent,子类Retrive)

class Retrive extends Component {
    constructor (props) {
        super(props);
        this.state = {
            name:''
        };
        this.getRemoteData = this.getRemoteData.bind(this);
    }
    getRemoteData (data) {
        this.state.retriveResult = data;
    }
    render(){
        return (
            <div className="Retrive">
                <Button name="search" onClick={this.getRemoteData}>查询</Button>
            </div>
        );
    }
}
复制代码

2.说一下你理解的模块机制

AMD: 异步模块加载规范。 a.js,定义一个依赖jQuery和echrts的组件。

define(['jquery', 'echarts'], function ($, echarts) {
  var AMD = function(){}
  AMD.prototype = {
       title:'',
        foo: function(){}//AMD类或者继承AMD类的子类的属性
  }
  function bar(){}//返回,公共属性
   function baz(){} //未返回,私有属性
  return {
       main:AMD,
       bar: bar
  }
});

复制代码

如果b.js依赖a.js,可以这样

define(['./a'], function (a) {
     //调用构造函数,foo
     var instance_amd = new a.main();
     instance_amd.foo()
      //调用bar
     a.bar()
});
复制代码

ES6 modules: 和python的包机制很类似,导入import,导出export。

1.场景:vue,react推荐机制,需要babel转义成es5以兼容浏览器。
2.关于import...(from...)
①.import...from...的from命令后面可以跟很多路径格式,若只给出vue,axios这样的包名,则会自动到node_modules中加载;若给出相对路径及文件前缀,则到指定位置寻找。
②.可以加载各种各样的文件:.js、.vue、.less等等。
③.可以省略掉from直接引入。
3.关于export
①.导出的可以是对象,表达式,函数,类
②.导出是为了让别人导入
4.言外话:使用es6的话,有一个特别好的规范去遵守,airbnb的es6规范(https://github.com/airbnb/javascript)
复制代码

CommonJS:nodejs中使用较多,关键词是require,没写过node包,只引用过别人的模块,所以内部实现原理不是很清楚。

3.MVVM原理

MVVM是一种软件架构模式,MVVM有助于前后端分离。 View:视图层,粗略理解为DOM。 Model:与数据库对应的model,一般为json格式,作为req的body通过http(s)与数据库实现通信。 ViewModel:View与Model通过ViewModel实现双向绑定。

核心是提供对View和ViewModel的双向数据绑定,这样使得ViewModel的改变View立即变化,MVVM在前端的实现有:angular,vue,react。

vue中的常用数据双向绑定。

view:{{message}}
viewModel v-model="message"
model:message
复制代码
<div id="app-6">
  <p>{{ message }}</p>
  <input v-model="message">
</div>

var app6 = new Vue({
  el: '#app-6',
  data: {
         message: 'Hello Vue!'
  }
})
复制代码

单文件组件中的话,就多了一个用html5的template标签将view和viewModel包裹起来,model部分停留在script标签部分。

<template>
     view
     viewModel
</tamplate>
<script>
     model
</script>
<styles>
     为了让view好看点
</styles>
复制代码

react的话,我在使用的过程中,没有听说过双向绑定的东西,对redux reducers推荐写为纯函数印象深刻,纯函数的话,感觉应该有点单项数据流的意思。

既然说到框架了,说一个最让我感觉有趣的点,那就是组件间的通信,对于简单组件,只涉及父子级别的通信的,vue使用on emit的方式,react使用props。对于复杂级别通信,爷爷父亲儿子孙子等等时,vue推荐使用vuex,react推荐使用redux,统一的全局状态树用来做状态管理非常好,可以使得逻辑非常清晰。vue项目文件结构研究不深,react的项目文件结构的话,presentational和containers的设计方法感觉非常有道理,一个负责视图一个负责数据,非常清爽。

4.最熟悉的框架路由机制

vue路由依赖:vue-router 通过组合组件来组成单页应用程序,只需要将组件映射到路由即可。 前端路由的核心,就在于 —— 改变视图的同时不会向后端发出请求。 需要注意2种模式的区别:hash模式和history模式,hash模式会在后面加一个很丑的#,可以开启history去掉。 hash模式原理:它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。hash可以理解为锚点,例如./index.html/#/foo,hash值为#/foo,这样不会跳转页面。就相当于统一页面的不同锚点,页面间跳转与 ./index.html/#foo到./index.html/#bar类似。

./store/index.js

import Router from 'vue-router'
Vue.use(Router)
export default new Router({
  routes: [
  {
  path: '/common',
  name: 'common',
  component: Common
  }
]

复制代码

路由层面还会包括嵌套路由,动态路由以及重定向,相当于自己模仿浏览器请求然后服务器响应模式,其实不涉及向后端请求,仅在浏览器就可以实现页面跳转,前段时间我做的用户权限控制就用到了vue-router,相比MVC结构下的后端路由,清晰了不少,这样后端只要负责路由编写api就好。

5.状态管理

下面是我在用vuex做项目时的一些思考,简单修改了一下,也添加了一些关于redux的思考。

vuex state,前端data view,前端DOM actions,用户操作,引起data变化从而导致DOM变化。

多个组件(视图)共享状态:通俗来讲,就是多个组件间会通信时,导致从后端拿来的数据发生变化,当组件较多时,如果兄弟组件间的通信都依赖父组件进行通信,会导致组件间的耦合非常高,从而导致项目逻辑混乱,难以维护。

多个组件(视图)依赖于同一状态。 来自不同视图的行为需要变更同一状态。

全局单例模式管理,把组件的共享状态抽取出来 不管在组件树的哪个位置,任何组件都能获取状态或者触发行为!

实践出真知:

1.state存放在index.js中,创建的Store实例getter,mutations,actions等,可以分离出来
2.getters存放在getter.js中,数据流为state→getter→组件,getter相当于一个数据获取过滤器,从仓库拿特定数据到组件,相当于对computed的集中处理。
3.mutations存放在mutations.js中,数据流为组件→mutations→state,mutations相当于一个数据提交发射器,从组件提交数据到仓库
4.actions存放在actions.js中,数据流为组件→actions→mutations→state,异步操作的主要场所。
5.modules是开发大型应用时需要用到的,每个module都有单独的states,getters,actions以及mutation,有一股nodejs模块的味道。
复制代码

vuex三原则:

1.唯一数据源
2.保持状态只读
3.数据改变只能通过纯函数完成 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
复制代码

一定要注意mutation和action的区别!

mutation只变更本地的状态,也就是说,直接只去修改store中的数据。 action包含异步操作,直接调用api,通过api的数据,再提交mutation。

可以说,action只比mutation多了一个异步调用api的操作,因为调用api后,一般有2种返回结果,succes或者error,或者是promise的多种状态,根据不同的。

最近在学习redux,组件dispatch一个action到store,相当于发送一个http请求,然后store做出响应,返回一个response给组件。和vuex大致类似,唯一有区别的是,vuex还需要引入react-redux,引入Provider和connect连接组件和store。

6.统计字符串中单词出现次数

" hi how are you i am fine thank you youtube am am ",统计"you"出现的次数。

function wordCount(str,word){
  var str = str || "";
  var word = word || "";
  var strArr = str.split(" ");
  var count = 0;
  for(var i=0;i<strArr.length;i++){
      if(word===strArr[i]){
          count++
      }
  }
  return count;
}
wordCount("hi how are you i am fine thank you youtube am am","you");
复制代码

如果字符串没有空格怎么办?

function wordCount(str,word){
  var str = str || "";
  var word = word || "";
  var count = 0;
  var index = str.indexOf(word);
  while(index!==-1){
      count++;
      str = str.substr(index+word.length);
      index = str.indexOf(word)
  }
  return count;
}
wordCount("hihowareyouiamfinethankyouyoutubeamam","you");
复制代码

如果不用js内置字符串函数,自己用每个字符对比呢?

function wordCount(str,word){
  var num = 0;
  var str = str+"  " || "";
  var word = word || "";
  var strArr = str.split("");
  var wordArr = word.split("");
  var count = 0;
  function compare(arr1,a,arr2,b){
        if(b+a<arr2.length){
          if(arr1[a]===arr2[b+a]){
            num++;
            return compare(arr1,a+1,arr2,b+1)
          }
          if(num===arr1.length){
            count++
            num = 0;
          }
        }
  }
  for(var i=0;i<strArr.length;i++){
      for(var j=0;j<wordArr.length;j++){
        if(wordArr[wordArr.length-1]===strArr[i+wordArr.length-1]){
          compare(wordArr,0,strArr,i+0)
        }
      }
  }
  return count;
}
wordCount("hihowareyouiamfinethankyouyoutubeamam","am");
复制代码

可以更加高效一些吗?

function wordCount (str,word) {

  var str = str+"  " || "";
  var word = word || "";
  var strArr = str.split("");
  var wordArr = word.split("");
  var wordArrLen = wordArr.length;
  var count = 0;
  var num = 0;

  function compare (arr1,a,arr2,b) {
        if(b+a<arr2.length){
          if(arr1[a]===arr2[b+a]){
            num++;
            return compare(arr1,a+1,arr2,b+1)
          }
          if(num===arr1.length){
            count++;
            num = 0;
          }
        }
  }

  var j = 0;
  while(j<wordArrLen){
      var i = 0;
      while(i<strArr.length){
          if(wordArr[wordArrLen -1]===strArr[i+wordArrLen -1]){
            compare(wordArr,0,strArr,i+0);
          }
          i++;
      }
      j++;
  }
  return count;
}

wordCount("hihowareyouiamfinethankyouyoutubeamam","a");

//1.调整最高层级遍历数组,从37的2次方降到3的2次方,从1369降到9
//2.合并控制变量和控制条件,使用while替代for,去除JS引擎查询i,j是否存在的消耗,会稍微降低代码可读性
//3.对重复引用的wordArr.length,赋值给局部变量wordArrLen,在这里,Array.prototype.length的查询次数从3次降低到1次
复制代码

面试2 - offer2 莫愁前路无知己,天下谁人不识君

我本来就不想再继续找了,但是今天朋友给我一个建议,跳槽前多投几家,说不定可以找到更好的,然后我就想到 V 站上看到好多求职贴,所以就索性再试试看有没有更好的坑。

简历: 前端开发-简书-趁你还年轻233

简书 : 趁你还年轻233

github: FrankKai

segmentFault:趁你还年轻

坐标:杭州

然后我在V站上发了贴,很有缘分,一个V友加了我的好友,约好时间进行了一次电话面试,后面了解好公司技术氛围和商议好薪资待遇后,成功拿到了offer,薪资待遇和第一家差不多,这家是浙大系的公司。

我和面试官很聊得来,最后甚至都聊到了吃鸡,由于电话面试太愉快了,所以这次电话面试到现在我仅记得一道跨域题了。第三家公司面试官也很赞,天气寒冷,友情提醒我注意路上冰冻,不过由于第三家面试在明天晚上才进行,所以我把第三家的BOSS直聘题也总结在一起了:一些特别棒的面试题[1]

贴一下面试题和我的回答。

1.平时有遇到过跨域问题吗?
2.下面这段代码最终输出什么?
3.["1","2","3"].map(parseInt)返回的是什么?
4.下面代码中“入库”的颜色是?
复制代码

1.平时有遇到过跨域问题吗?

说到跨域问题,就一定要说到同源,什么是同源,相同协议,相同域名,相同端口,即为同源。

不同源之间的通信就会有跨域问题,一般来说是客户端访问服务器,服务器上去配置跨域。 我遇到的跨域问题都是后端去配置一下就可以解决的,比如我前端在用vue的官方推荐异步请求库axios,去请求后端的koa开启的后端服务时,就会遇到跨域的问题,例如koa使用依赖koa-cors就可以,具体的话,就是Access-Control-Allow-Origin: 源名,可以为*或者是特殊的源。或者是传统的maven或者nginx上,也可以很方便的配置跨域。

JSONP有用过吗?JSONP没用过,但是原理貌似是通过js加载一个script DOM标签进来,然后在新的script的src中引入想要执行的代码。 其实跨域问题在后端中也有类似的,只不过是叫做进程间通信,有IPC,RPC等等方式进行进程间通信。

2.下面这段代码最终输出什么?

let O = function(name){
 this.name = name || 'world';
};
O.prototype.hello = function(){
 return function(){
  console.log('hello ' + this.name);
 };
};
let o = new O;
let hello = o.hello();
hello();
复制代码

年轻的我的答案是:hello world。

答案显然是不对的,因为这是一道陷阱题,陷阱就在于O.prototype.hello调用后,return的是一个函数,这么做的话,在执行新实例o的hello方法是,this其实已经变成了window。

那么答案是hello undefined吗?

年轻的你又错了,并不是。

而是 hello 。

请注意,是hello ,而不是hello undefined,而是空字符串

原因就在于window.name是事先是有定义的,而且其值为空。

不信的话你可以在控制台打印window.name,返回的是"",你再打印window.yourname试试看,比如window.frank,返回的就是undefined了。

感谢@ygh1的提醒,打印结果和运行环境也是有关的,因为node中全局是global,browser中全局是window。

刚在node里跑了下有陷阱的题目,打印出来确实是hello undefined,因为node中的global对象没有初始的name属性。

所以最正确的答案应该是:

node环境:hello undefined
browser环境:hello _____(非零宽空字符)
复制代码

而我在工作中,遇到的更多的常见的像上面一样的工厂函数式的写法是这样的。

let O = function(name){
 this.name = name || 'world';
};
O.prototype.hello = function(){
  console.log('hello ' + this.name);
};
let o = new O("frank");
let hello = o.hello("frank");
复制代码

打印结果为:hello frank。

如果不传入frank的话,打印出的是默认值hello world。

3.["1","2","3"].map(parseInt)返回的是什么?

A. [1,2,3] B.["1","2","3"] C.[1,1,1] D.其他

这特么又是一道陷阱题,还好我之前在看MDN的map函数时,看到过这个陷阱。

正确答案是D:其他。

其他的具体值为多少?[1,NaN,NaN]。

不敢相信吧,为什么不是可爱的[1,2,3]呢?

因为map的callback有3个参数,currentValue,index和array,parseInt有2个参数,string和radix(进制),只传入parseInt到map中的话,会自动忽略第三个参数array,但是不会忽略index,所以就会把0,1,2作为第二个参数传给parseInt。

如果还不明白的话,我们把["1","2","3"].map(parseInt)的每一步都拆开来。

parseInt("1",0) 此时将字符"1"转换为O进制数,由于0进制数不存在,所以返回Number类型的1。
parseInt("2",1) 此时将字符"2"转换为1进制数,由于超出进制数1,所以返回NaN。
parseInt("3",2) 此时将字符"3"转换为2进制数,由于超出进制数2,所以返回NaN。
复制代码

至此,真相大白。 那么常用的非陷阱式map写法是怎样的呢?

像这样:["1","2","3"].map(x=>parseInt(x))

传一个完整的函数进去,有形参,有callback,这样就不会造成因为参数传入错误而造成结果错误了,最后返回一个漂漂亮亮的经由纯函数处理后的新数组回来。

其实这里如果再深入的话,可以再考察纯函数是什么?

纯函数其实就是一个不改变输入,但是可以借助输入,产生一个以输入为原材料,经过加工处理后,输出一个全新的输出的函数,关键在于不改变输入,纯函数是编写redux的reducer必须具备的技能点。

刚才公司的大牛过来,说他从来不用parseInt,他用加号,+"1" 返回1,+"2"返回2。大牛果然大牛,黑科技是真的多。

4.下面代码中“入库”的颜色是?

<ul class="list" id="list">
 <li class="favorite">
  <span>出库</span>
 </li>
 <li class="favorite">
  <span class="highlight">入库</span>
 </li>
</ul>
<style>
#list .favorite:not(#list) .highlight{
 color: red;
}
#list .highlight:nth-of-type(1):nth-last-of-type(1){
 color: blue;
}
</style>
复制代码

A. red B.blue C.black

我的答案是:我猜一下,可能是A,因为A的权重是最大的,伪类选择器的权值应该比较小吧。

面试官发来一个👍,明天可以来公司面谈吗?已经约好明天面试。

这道题的解答到此为止,因为我是真的真的对CSS不感兴趣,各位看官老爷请原谅我。

面试-3 未通过

1.说下下面两种font-size单位的异同?

em rem

二者的为了保证用户修改字体大小时,保持垂直方向上的字体大小一致。与px不同,二者都是字体计算大小单位,也就是说,需要通过计算得出其大小,转换成px,微信小程序的rpx也是这样,最后还是转换成了px,可能是借鉴了rem的思想吧。

但是em相对于继承来的父元素,rem相对于根元素。听大牛说,rem在国内使用比较多,可能侍使用习惯问题。我自己也觉得rem使用起来更简单,为根元素的font-size赋一个初始值,再配合css的媒体查询,可以动态的改变这个全局的单位,可以说是牵一发而动全身,使用起来非常方便,而em的可阅读性就很差了,有的时候为了算字体大小,需要一级一级找上去,非常不直观。

现代的常用的浏览器,1rem等于16px,但是可以通过html{font-size:percentage/num }来控制。

举2个 rem和em例子对比下。

html简写结构:

<html>
    <body>
        <div></div>
    </body>
</html>
复制代码

rem 例子:

html { font-size:62.5%; }  /* =10px */
body { font-size: 2.0rem; } /* =20px */
div   { font-size: 1.0rem; } /* =10px */
复制代码

em 例子:

html { font-size:62.5%; }  /* =10px */
body { font-size: 2.0em; } /* =20px */
div   { font-size: 1.0em; } /* =20px */
复制代码

MDN的font-size章节给出了em和rem的非常好的解释,英文原版非常直观,我这里再贴一下:

em

Represents the calculated font-size of the element. If used on the font-size property itself, it represents the inherited font-size of the element.

rem

Represents the font-size of the root element (typically ). When used within the root element font-size, it represents its initial value (a common browser default is 16px, but user-defined preferences may modify this).

其实em和rem与MVVM框架的组件间通信有些类似,都有逐级继承和全局影响的概念。em是逐级传递的,也就是继承,框架中用props和事件订阅发布的方式也是这样,爷,父,孙的传递都是要一级一级去传递的,爷爷想直接传授点技能给孙子必须先传授给父亲,爷爷→父亲→孙子;而rem就和框架中的状态管理库很像,例如vuex和redux,抽取出一个全局的状态树,不用一级一级的去很复杂的去继承,爷爷想教教孙子,直接就可以传授给孙子,爷爷→孙子

2.只用一个div 实现定时红绿灯

default

<!DOCTYPE html>
<html lang="zh-cn">
    <head>
        <meta charset="utf-8" />
        <title>仅用一个DIV实现红绿灯</title>
        <style>
        	@keyframes light{
        		0%{
        			background-color: green;
        			left: 0;
        		}
        		33.3%{
        			background-color: green;
        			left: 0;
        		}
        		33.4%{
        			background-color: yellow;
        			left: 200px;
        		}
        		66.6%{
        			background-color: yellow;
        			left: 200px;
        		}
        		66.7%{
        			background-color: red;
        			left: 400px;
        		}
        		99.9%{
	       			background-color: red;
        			left: 400px;
        		}
        	}
        	.traffic-light{
        		position: relative;
        		width: 200px;
        		height: 200px;
        		border-radius: 50%;
        		animation: light 3s ease-in-out 0s infinite;
        	}
        	.container{
        		width: 600px;
        		border:10px solid #000;
        		border-radius: 20% 20%;
        	}
        </style>
    </head>
    <body>
    	<div class="container">
    		<div class="traffic-light"></div>
    	</div>
    </body>
</html>
复制代码

面试-4 未通过

由于种种原因,开始了新一轮的面试,这一轮面试可谓收获颇丰。 与各种各样的面试官交流下来,除了收获到一些疏漏的知识点外,发现面试其实非常考验面试官的水平。揣摩出一些如何成为一名合格的前端面试官方法。

以及很重要的老哥的经验:进大厂前必须要做的准备,提前一个月刷题。

1.setTimeout与函数调用栈

console.log(1);
setTimeout(function(){
    console.log(2);
},0);
console.log(3);
复制代码

输出:1 3 2 原因:Call Stack会最后调用setTimeout的callback,setTimeout中的callback是一个异步函数。 函数调用栈的部分可以参考这里:blog.risingstack.com/node-js-at-…

2.function foo 与 var foo的提升优先级

console.log(typeof foo);
var foo = "foo";
function foo(){}
复制代码

输出:function

console.log(typeof foo);
function foo(){}
var foo = "foo";
复制代码

输出:function

function优先级比var高,无论在其前后,都会覆盖掉同名的var声明。

3.let 块作用域 与 setTimeout

for(let i=0;i<6;i++){
    setTimeout(function(){
        console.log(i)
    },0)
}
console.log(i)
复制代码

输出:

0
Uncaught ReferenceError: i is not defined
1
2
3
4
5
复制代码
  • Uncaught ReferenceError: i is not defined 由此可见for语句的块作用域,不仅仅在花括号中生效,在圆括号中也生效。
  • 输出0 1 2 3 4 5 的原因 setTimeout的callback是异步函数,for循环实质上是在做异步循环队列,setTimeout的callback会被调用5次,由于let会为每次的i分配独立的地址空间,因此每一次传不同的值进去。

为什么在debug的过程中,打印顺序是混乱的? (等把规范的timers章节翻译完,再来解决) breakpoint打在console.log(i)上。

Uncaught ReferenceError: i is not defined
0 
2
5
4
3
1
复制代码

如果将let替换成var呢?

for(var i=0;i<6;i++){
    setTimeout(function(){
        console.log(i)
    },0)
}
console.log(i)
复制代码

输出: 6个6 原因:

  • 打印的是window.i 每个传入的i指向相同的i,传入时依次window.i的值为1,2,3,4,5,6,但是都是同一个引用,当函数调用栈开始执行setTimeout的callback时,window.i已经变为了6
  • var 不会限制块作用域 不会分配6个独立的地址空间给setTimeout的callback

4.为什么Object.toString.call([1,2,3])返回[object Array]?[].toString()可以返回[object Array]吗?

若想回答这个问题,需要深入理解Object.prototype.toString.call()

难道真的像自己理解的那样,是通过call将[1,2,3]作为Object.toString的实参传递了进去吗?不是。 直接Object.toString([1,2,3])不能实现同样的功能吗?不能。 而实际上也有Array.proto.toString()这种形式,所以是可以直接调用arr.toString()的,这样能检测出吗?不行。

那到底是什么原因? 先来肝一个表格。


数据类型 例子 return
字符串 "foo".toString() "foo"
数字 1.toString() Uncaught SyntaxError: Invalid or unexpected token
布尔值 false.toString() "false"
undefined undefined.toString() Uncaught TypeError: Cannot read property 'toString' of undefined
null null.toString() Uncaught TypeError: Cannot read property 'toString' of null
String String.toString() "function String() { [native code] }"
Number Number.toString() "function Number() { [native code] }"
Boolean Boolean.toString() "function Boolean() { [native code] }"
Array Array.toString() "function Array() { [native code] }"
Function Function.toString() "function Function() { [native code] }"
Date Date.toString() "function Date() { [native code] }"
RegExp RegExp.toString() "function RegExp() { [native code] }"
Error Error.toString() "function Error() { [native code] }"
Promise Promise.toString() "function Promise() { [native code] }"
Obejct Object.toString() "function Object() { [native code] }"
Math Math.toString() "[object Math]"

为什么会出现下面的情况?

Object.toString.call(Array)//"function Array() { [native code] }"
Object.prototype.toString.call(Array)//"[object Function]"
复制代码

答案在这里!

Object.toString()//"function Object() { [native code] }"
Object.prototype.toString()//"[object Object]"
复制代码

Object对象和它的原型链上各自有一个toString()方法,第一个返回的是一个函数,第二个返回的是值类型。

既然知道了不同,现在我们再来分析下Object.prototype.toString.call(Array)//"[object Function]"。 Array对象本身返回一个构造函数,Array//ƒ Array() { [native code] },而Object.prototype.toString()返回的是//"[object Type]"的形式,通过call将Array的this上下文切换到Object,从而调用了Object.prototype.toString(),因此返回[object Function]

需要注意的是:Math.toString()直接返回"[object Math]"。

实际开发中,我们用到最多的可能是:Object.prototype.toString.call([1,2,3])//"[object Array]"这种。

总结:

  • 一般情况下,js中对象的toString(),返回字符串,内容与函数声明语法有关,例如[1,2,3].toString()//"1,2,3"
  • 大多数都返回函数的完整源码,Array.toString()//"function Array() { [native code] }"
  • 内置函数往往返回一个类似"[native code]"的函数体,需要配合call方法,比如Object.prototype.toString.call([1,2,3])//"[object Array]"

那么不可以直接Array.prototype.toString.call([1,3,4])吗? 不行! 因为Array,Function,Date虽然是基于Object进行创建的,但是他们继承的是Object.toString(),而不是Object.prototype.toString()。 再加深一遍印象:

Object.toString()//"function Object() { [native code] }"
Object.prototype.toString()//"[object Object]"
复制代码

所以这就是必须用Object.prototype.toString()去检测类型的原因。

至于Object.prototype.toString()内部是怎么实现的,等到时机成熟再去深入。

5.综合考察bind,call和apply的面试题

var obj = {
    a: 1,
    name: 'world',
    objSayName: function (fn) {
    	fn();
    }
}
function sayName () {
	return console.log(this.name);
}
obj.objSayName(sayName);
// 输出:undefined
复制代码

为什么? 在obj的objSayName内部,没有修改this指向到当前调用对象。

题目一:对象内部方法,调用全局的函数 适用:多个对象(局部方法)复用同一全局函数 精简:局部(方法)复用全局函数 方法:修改this指向,通过Function.prototype.bind()去显式修改this指向到当前调用对象。 原因:Calling f.bind(someObject) creates a new function with the same body and scope as f, but where this occurs in the original function,in the new function it is permanently bound to the first argument of bind, regardless of how the function is being used.bind only works once!

var obj = {
    name: '1',
    objSayName: function (f) {
      var g = f.bind(this);
	  console.log(g());
    }
};
function sayName(){
    return this.name;
}
obj.objSayName(sayName);

复制代码

输出:'1'

拓展: 题目二:如果全局方法想输出对象的局部属性,该怎么办? 适用:同一全局函数输出多个对象(内部变量) 精简:全局函数输出局部(变量) 方法:使用apply或者call修改this指向到被调用对象 原因:An object can be passed as the first argument to call or apply and this will be bound to it.

var obj = {
    name: '1',
    say: function (fn) {
        fn();
    }
};
function sayName(){
    return this.name;
}
sayName.apply(obj);
sayName.call(obj);
// 输出:'1'
复制代码

参考:developer.mozilla.org/en-US/docs/…

面试-5 offer 3 整个江湖都任我闯,我的生命像一首歌

现东家的面试题,是我最欣赏的一套面试题,在这里已经工作了接近1年时间,成长许多。

题目主要分为4个部分:

  • HTML
  • CSS
  • JS
  • 代码

答案未验证准确性,有待验证并更新。 我将按照“1 year before”和“today”的角度去进行解答,也是对自己成长的一个记录。

HTML

1.请描述cookies, sessionStorage和localStorage的区别

1 year before: cookies,需要与后端协作,一种认证方式,expires失效时间 sessionStorage,当前会话有效 localStorage,本地缓存,expires失效时间 today:

cookies
  • 一个HTTP cookie(web cookiebrowser cookie)是服务器发送给用户web浏览器的一小段数据段。
  • 浏览器可以把它存在自己本地,并且将它在下一个请求中发送给它的数据源服务器。
  • 典型的应用场景就是去判断两个请求是否来自同一个服务器----来自同一个登录用户,它是对无状态的HTTP协议的一种有状态记忆。
sessionStorage
  • key/value的方式存储,会话存储
  • 为每一个给定的origin维护一个分离的storage区域,在页面会话期间可用
localStorage
  • key/value的方式存储,持久化存储
  • 浏览器关闭后重新打开,localStorage的键值对仍然存在

2.请解释<script><script async><script defer>区别

1 year before: 常见的同步;异步;延迟加载。

today:

  • <script async>控制浏览器同异步加载脚本。若设置async为false,那么浏览器在HTML解析期间同步加载脚本。一般来说通过代码document.creatElement()插入脚本是异步的,设置async为false可以将其控制为同步。
  • <script defer>defer属性用来控制script是否在HTML完成解析之后再执行,但是会在DOMContentLoad燃烧之前完成。

拓展: 1.什么是DOMContentLoaded事件? DOMContentLoaded事件,会在初始化HTML document完成加载和解析触发,无需等待stylesheets,images,以及字frame结束加载。Load事件和DOMContentLoaded事件很类似,经常有人混淆2个事件。 2.什么是Load事件? load事件,会在完成一次完全加载之后再触发。 3.如何解决同步js阻塞DOM渲染的问题,最快速度渲染DOM? 异步化js优化stylesheets加载。 4.如何检查document是否完成加载? document.readyState

  • loading 加载中。
  • interactive document完成加载,document已经被解析但是子资源例如images、stylesheets和frame赈灾加载。
  • complete document以及子资源都已加载完成,此时会触发load事件。

所以一次加载文档资源的状态有3个,loading``interactivecomplete

3.为什么通常推荐将CSS 放置在<head></head>之间,而将js<script></script>放置在</body>之前?

1 year before: 防止阻塞浏览器渲染,先执行CSSOM,再绘制DOM,再操作DOM。主线程单线程渲染UI。

today 从技术实现的角度讲,先加载stylesheets并且绘制,DOM渲染结束后再加载并执行可能涉及DOM操作的js文件,避免js的执行导致渲染阻塞。 从用户体验的角度讲,优先加载样式表减少用户面对空白网页的时间,提升用户体验。

CSS

1.CSS中类(classes)和ID的区别

1 year before ①ID优先级高 ②ID唯一标识,不能重复

today ①语法不同,#idname, .classname ②影响元素不同,#idname作用单个元素,.classname作用所有元素 ③选择器权重不同,#idname权值高于.classname

2.有哪些隐藏DOM的方法

1 year before

  • display: none
  • position: top right bottom left
  • transform
  • z-index
  • 移除DOM节点(JS)

today [译]如何隐藏DOM元素?

3.请解释*{ box-sizing: border-box }的作用,并且说明使用它有什么好处?

1 year before 怪异盒模型。 计算方便,width包含了border,响应式百分比布局。

today box-sizing的值有2种,一个是content-box,一个是border-box,content-box仅仅包含content。 border-box的width会将content, padding和border包含在内,例如width:100%指的是包含了content,padding和border的宽度,布局时更好控制。

例如子元素继承了父元素的width:100%,此时设置了子元素padding,border,若子元素的box-sizing是content-box,会导致溢出,而border-box的话,width:100%会很舒服地包含了padding和border。

因为这样的应用场景很多,所以索性就为所有标签都设置成border-box,有特殊情况的再手动设置成content-box。

image

有一篇墙推IE怪异盒模型的文章:把所有元素的box-sizing都设置成border-box吧!

4.请问在确定样式的过程中优先级是如何决定的(请举例)?如何使用此系统?

1 year before

  • style属性 <div style=""></div>
  • id
  • 标签名
  • class
  • 伪类

每个选择器都有权值,权重为和,#foo>.class::before 三者和为权重,权值id>标签>class>伪类。

today 优先级由高到低

  • !important 不建议的实践
  • 行内样式 <span style=""></span>
  • ID选择器 #foo
  • 类选择器.foo,属性选择器[type="radio"],伪类:hover
  • 类型选择器p,span,伪元素::before

一个很优秀的说明css选择器优先级的图:specifishity.com/

image

通用选择器(*),组合符(+,>,〜,'',||)和否定伪类(:not())对权重没有影响。

5.请问为何要使用translate()而非absolute positioning,或反之的理由?为什么?

1 year before absolute

  • 从正常文档流移除
  • 不占据页面布局空间
  • 开辟新的stacking context
  • 影响其他布局元素
  • 消除边缘重叠

减少了计算,translate()不影响其他元素,性能更好,GPU计算次数更少

today 在我的这篇博问中有答案:CSS3动画卡顿性能优化解决方案

translate()涉及到的是合成器线程,与主线程是相互独立的,所以会比较快。 而absolute positioning涉及到的是主线程,会导致主线程负责的布局重绘和js执行,所以会比较慢。

JS

1..call和.apply的区别是什么?

1 year before

  • 传入参数方式不同,但都是动态修改this上下文
  • call 会有函数执行栈的操作
  • apply仅仅将arguments传到新的类中

today 关于这个问题曾经产出2篇博客: 从规范去看Function.prototype.apply到底是怎么工作的? 从规范去看Function.prototype.call到底是怎么工作的? call是索取,apply是付出。 从call和apply的字面意思就可以看出,call调用,apply应用,调用函数,应用参数。 call和apply的主要区别在于,call仅仅切换this上下文到其他类,从而调用自己不存在的方法;而apply主要是为了将其他类型的参数传递到自己内部,再调用自己的方法。

假设有foo,bar。 foo.call(bar) bar调用foo的方法,实例Object.toString([1,2,3])->"[object Array]",数组实例调用了Object类的toString方法。 foo.apply(null, bar) foo的方法应用bar的参数,Math.max.apply(null, [1,2,3])->3,Math的max方法应用了[1,2,3]中的每个参数。

2.请解释JSONP的工作原理,以及它为什么不是真正的Ajax?

1 year before JSONP 原理:异步插入一个<script></script>,会有XSS问题 原因:没有调用XMR对象 today 一种比较古老的不安全的跨域请求访问方式,没有调用XMR对象,服务器允许浏览器在query parameter中传递浏览器内定义的函数,而这个函数是有概率被XSS攻击改写的。

来自StackOverflow高票答案:stackoverflow.com/questions/2…

JSONP不是一个很复杂的问题。 假设我们在example.com域名下,此时我们想给example.net域名发送一个请求。为了请求成功,需要跨越域名边界,这对于浏览器来说是禁忌。

绕开这个限制的一种方式是<script>标签。当你使用script标签的时候,域名限制会被忽略,但是对结果不能做任何处理,脚本是被评估过的。

开始进入JSONP。当你发送一个请求到支持JSONP的服务器,你会传一些特殊的关于你的页面的数据给服务器。这种方式下,服务器会以你的浏览器处理起来方便的方式包装响应。

例如,服务器需要一个叫做callback的参数去开启JSONP功能。然后你的请求会想下面这样: http://www.example.net/sample.aspx?callback=mycallback

没有JSONP的情况下,这可以返回一些基本的JS对象,例如: {foo: 'bar'}

然而,在支持JSONP的情况下,服务器接收到callback参数,它包裹结果的方式是不一样的,会返回下面这样的数据: mycallback({foo: 'bar'});

就如你缩减,他会调用浏览器端的方法。所以,在你的页面上callback的定义如下:

mycallback = function(data){
  alert(data.foo);
};
复制代码

现在的话,在脚本加载成功后,它会被评估,然后函数执行。cross-domain请求成功!

所以JSONP存在一个很严重的问题:你失去了对请求的控制。例如,无法得知错误代码的返回。可以使用定时器去监控请求,但是这不是很好。JSONRequest是一个非常好的跨域脚本执行的方式,安全,并且获得更多对请求的控制。

2015年,CORS是一个与JSONRequest可以抗衡的方案。JSONP在老式浏览器下仍旧有用,但是不安全。 CORS是做跨域请求访问的更安全、更高效的一种方式。

3.用过javascript模板系统吗?都使用过哪些库?

1 year before jade vue angular react { ... }或者{{ ... }}

today: 一些好用的模板引擎库

常用的还是前端框架自带的以及pug,由于我只对vue.js的比较熟悉所以就没有罗列react和angular的demo。

4.== 和 === 有什么不同?

1 year before 这个问题写过博客,看过规范。 ①===是==的子集 ②==有类型转换 ③规范内实现机制不同 today 你真的理解==和===的区别吗?

5.请解释Javascript的同源策略(same-origin policy)

1 year before 浏览器安全机制

  • same-origin:相同协议,相同host,相同端口
  • 跨域问题:CORS 可以后端设置允许 cross-origin-access信息

today 如何理解same-origin policy?

6.你使用过Promises及其polyfills吗?请写出Promise的基本用法(ES6)

1 year before axios

  • axios get() post()
  • 前端请求、响应拦截器

today

  • Promise.all() 只有当所有的异步Promise执行完为resolved状态时,返回一个Promise
  • Promise.prototype.catch() 捕捉Promise执行过程中捕捉到的异常
  • Promise.prototype.finally() 是catch和then的并集,常用于在catch和then中都运行的代码,避免重复
  • Promise.prototype.then() Promise执行完毕,进行下一步
  • Promise.race() Promise竞争,返回Promise集合中最先resolve或者reject出来的Promise;常用于时间控制、中止Promise 可以参考我发的一篇帖子:什么情况下会用 Promise.race()?
  • Promise.reject() Promise类捕捉异常
  • Promise.resolve() Promise类解析数据

7.使用Promises而非回调(callbacks)优缺点是什么?

1 year before 解决了callback hell问题。

today 以一个图片资源加载绘制canvas。

class CanvasImage {
  constructor(url, width, height) {
    this.width = width || 500;
    this.height = height || 500;
    this.url = url || '';

    this.element = new Image(this.width, this.height);
  }
  get imageElement() {
    return this.element;
  }
}
复制代码

callback hell方式

source.onload = function() {
  const target = new CanvasImage(url, 80, 80).imageElement;
  target.onload = function() {
    const main = new MainCanvas(source, target, 'right').canvasElement;
    context.drawImage(main, 0, 0, 500, 500);
  };
};
复制代码

Promise方式

const sourcePromise = new Promise((resolve) => {
  setTimeout(() => {
    const target = new CanvasImage(url, 80, 80).imageElement;
    resolve(target);
  }, 0);
});
source.onload = function() {
  sourcePromise.then((target) => {
    const main = new MainCanvas(source, target, 'right').canvasElement;
    context.drawImage(main, 0, 0, 500, 500);
  });
};
复制代码

async/await方式

 function sourceCanvasImage() {
  return new Promise((resolve) => {
    setTimeout(() => {
      const target = new CanvasImage(url, 80, 80).imageElement;
      resolve(target);
    }, 0);
  });
}

async function mergeCanvas() {
  const targetElement = await sourceCanvasImage();
  const main = new MainCanvas(source, targetElement, 'right').canvasElement;
  context.drawImage(main, 0, 0, 500, 500);
}

source.onload = function() {
  mergeCanvas();
};
复制代码

8.什么是事件循环(event loop)?

1 year before

  • 异步原理
  • 事件订阅、事件发布
  • 异步非阻塞式事件

today 官方的解释更加权威:nodejs.org/de/docs/gui…

什么是Event Loop?event loop允许Node.js执行非阻塞的I/O操作-----尽管JS是单线程的-----它尽可能地通过把操作卸载到系统内核(kernel)。

因为大多数现代的内核(kernel)是多线程的,它们可以在后台处理多个操作。当其中之一完成后,kernel会告诉Node.js一个适当的callback可以被添加到poll queue(轮询队列)中并且执行。

更多原理上的内容,可以参考:[译]Node.js Event Loop,Timers和 process.nextTick()

event loop,原理细节包括以下内容

  • call stack
  • (macro)task queue
  • microTask queue
  • background thread

其中前三部分属于main thread,可以阅读node源码一探究竟。最后的background thread属于libuv的部分,可以去深入libuv源码(这是一个专门处理异步的c语言库)理解其实现机制。

但是阅读源码需要非常好的基础。这里推荐一篇囊括了以上知识点的非常走心的文章:blog.risingstack.com/node-js-at-…

代码

1.foo的值是什么?

var foo = 10 + '20';

1 year before: 30

today: "1020"

2.如何实现以下函数?

add(2, 5);// 7
add(2)(5);// 7
复制代码

1 year before 通过arguments类数组对象。

function foo(){
    var sum = 0;
    for(var i = 0; i<arguments.length; i++){
        sum += arguments[i]
    }
    if(arguments.length>=2){
        return sum;
    }else{
        return foo;
    }
}
复制代码

add(2)(5)运行失败。

today

const add = (a, b) => {
    const result =
      a && b
        ? a + b
        : function(b) {
            return a + b;
          };
    return result;
 };
复制代码

3.下面两个alert的结果是什么?

var foo = "Hello";
(function() {
    var bar = " world";
    alert(foo + bar);
})();
alert(foo + bar);
复制代码

1 year before "undefined World" "Hello undefined"

today "Hello world" // 立即执行函数作用域可以访问全局变量 Uncaught ReferenceError: bar is not defined // 全局作用域不可以访问局部变量

4.下面代码的输出是什么?

console.log('one');
setTimeout(function() {
    console.log('two');
}, 0);
console.log('three');
复制代码

1 year before one three two

today one three two

原因:优先执行函数调用栈中任务,setTimeout排入宏任务队列延后执行。 event loop包括call stack,(宏)任务队列,微任务队列和background thread,而call stack中的普通代码优先执行,setTimeout会经由background thread进入宏任务队列,宏任务队列间隙执行微任务队列。

image

5.下面代码的输出是什么?

function getResult(value, time){
    return new Promise((resolve)=>{
        setTimeout(()=>{
            console.log(value);
            resolve();
        }, time);
    });
}
(async () => {
    const a = getResult(1, 300);
    const b = getResult(2, 200);

    await a;
    await b;
})();
(async () => {
    await getResult(1, 300);
    await getResult(2, 200);
})();
复制代码

1 year before 1 2

today 2 1 1 2

这是一道考察async的题目:如何理解async函数?

6.下面的代码输出是什么?

var obj = {
    a: 1,
    name: 'world',
    objSayName: function(fn) {
         fn();
    }
}
var name = 'hello';
var arr = [1, 2, 3, 4, 5];

function foo(o) {
    var bar = arr || [6, 7, 8];
    var arr = [4, 2, 9];
    var baz = o;
    baz.a = 2;
    console.log(bar, obj.a);
}

function sayName() {
    return console.log(this.name);
}

foo(obj);
obj.objSayName(sayName);
复制代码

1 year later [4,2,9] 2 'hello'

today [6,7,8] 2 'hello'

原因: 变量提升。

var bar,arr,baz;
bar = arr || [6, 7, 8];// 此时arr是undefined,因此bar得到了[6, 7, 8]赋值
arr = [4, 2, 9];
复制代码

baz获得了obj的引用,所有修改baz相当于修改obj,所以打印出2.

因为闭包,全局的sayName函数内的this指向全局,所以是hello。

我想成为一个很厉害的人,让这个世界因为有了我而有一点点不一样,哪怕,只有一点点

贴一下自己理想中的团队的关注点

1.个人前端技术的成长空间

比如资深前端对初级前端的指导,前端团队成员间的学习和提升等等 这个最重要,我的目标是2年时间成为基础扎实,有独立前端技术见解的开发者

2.公司对员工成长和进步的重视程度

例如新人培训,技术分享,技术翻译等等

3.公司加班情况是996还是10105或者是按照项目进度安排的

只要是一帮追求前端极致的人,996是完全OK的

4.员工补贴,福利

例如过节补贴,工会福利

5.绩效考核和奖金

例如绩效考核,年终奖金

这次年后换工作我希望自己能在一家公司呆至少2年以上,所以这些方面我都需要了解清楚再做决定,需要慎重一些,希望能够加入一个有趣并且有实力的团队~

简历戳这里: 前端开发-简书-趁你还年轻233

最后再说句体题外话,3家公司都在用vue,都问我有没有用过vue...

That's it !

趁你还年轻

2018年1月31日

于冻成狗的冰雪杭州

如何成为一名合格的面试官?

  • 学会倾听 无论面试者有多少职场上的不如意想和你倾诉,都要耐心听他讲完,不要打断他
  • 学会定位 根据面试者的工作经验和工作场景,对能力进行简单定位,虽然这偶尔会有所偏颇,但是对于后续提问会有很好的帮助
  • 学会理解 理解面试者想要表达的对知识点的理解,即使他讲的很模糊,尽可能捕捉其理解的部分
  • 学会引导 当面试者遇到一道棘手的题目时,如果对方逻辑不清楚,学会化繁为简,由浅入深去做引导;假设对方对当前的题目游刃有余,引导深入原理或者是扩展出应用场景
  • 学会挖掘 学会挖掘出面试者身上的闪光点,聪明,执着,这些都是非常好的品质
  • 学会尊重 当面试者急于想得知面试官的评价时,无论你对他是否满意,都采取"如果有新的进展,我们后续会再通知你"的方式
  • 学会沉默 当面试者想得知你对问题的看法时,不要做太深层次的讲解,点到为止,因为有限的面试时间主要目的是为了考察面试者的能力
关注下面的标签,发现更多相似文章
评论