源:鱼头的Web海洋
之前刷某乎的时候,看到这么一个问题:“如何衡量一个人的 JavaScript 水平?[2]”然后自己也不要脸地回答了一下这个问题。以下是我的答案:
原文如下:
A:看一个人写代码是否有规范,代码是否壮健,是否可拓展,可读性高不高,API设计是否合理。
这些都是长年累月积累下来的且独立于编程语言以外的。
远比把什么手写bind,原型链,闭包给背下来更有价值。
这才是证明你代码水平的关键点。
Q:在面试的时候如何快速判断出呢?
A:让面试者设计个组件,不用写,回答就行。从API设计,文档编写,项目结构,单元测试,编写模式,性能优化等方面来回答。
有工作经验的人,基本业务逻辑都能写,但是写的好不好,就是经验跟能力以及学习力的体现 。
首先来个免责声明,以上的回答都是个人的经验与见解,答案肯定不唯一,甚至不一定全对,所以求轻喷。
上面问如何在面试的时候快速判断对方是否是高级前端的时候,我为什么说是“设计组件”呢?
因为我觉得有一定实力的前端来说,“组件”这个概念是绕不过的,或者看过开源组件的源码,或者自己写过组件。
对于一般的业务问题,我相信作为一个从业了一定时间的开发者,无论水平如何,这都不是问题,但是如何区分这个开发者的水平,可以通过他写的代码来判断,当然也不完全是,毕竟在996或者赶进度的时候,很容易就会为了完成快速出产品而写,这种情况下代码质量跟个人水平不一定能体现。
下面,我们以设计一个“按钮(<Button>)组件”为例,来探索这个问题。
首先“按钮(<Button>)”的作用这个我们是否明确?它是装饰性的组件还是功能性的组件?
这个问题很简单,“按钮(<Button>)”是一个功能性的组件,是让用户通过点击或触碰来采取行动并做出选择的一个组件。
那么“按钮(<Button>)”通常放在什么地方?有经验的开发可能会想到以下场景:
?对话框?模态窗口?表单?卡片?工具栏
代表状态可能会有以下几种:
?默认状态?初始状态?信息状态?警告状态?危险状态
形态可能会有以下几种:
?实心按钮?文本按钮?描边按钮?图标按钮?圆角按钮?直角按钮
尺寸可能会有以下几种:
?small?medium?large
操作性可能会有以下几种:
?回车键点击?鼠标点击?触摸点击?禁止点击
携带的事件可能有以下两种:
?click事件?回车键keydown事件?tap事件
以上虽然是偏样式,但是作为一个组件开发者,这都是我们日常所需要考虑的。
在API的设计环节,我们通过上述的场景,我们可能会暴露出以下的API
?type:按钮状态?size:按钮尺寸?color:按钮颜色?text:按钮内的文本?icon:按钮内的图标?htmlType:原生按钮支持的type属性?attrs:其他的原生属性?variant:按钮形态?click:鼠标点击事件?tap:触摸屏点击事件?keydown:回车键按下事件
在我们API设计好之后,我们就可以开始开发了,这时候根据我们项目的类型,选择的开发工具以及模式,可能会有所不同。
我们是独立编写还是直接在项目里面去编写,如果是独立编写,选择哪个打包工具,是gulp还是webpack还是其它,为什么这么选?
例如如果我们是用TS写,我们可能需要编写Button.d.ts,如果是vue的组件,我们还得考虑Vue.use注入到Vue中,也就是Button.install(vue),如果是react,我们还得考虑是否使用React.forwardRef来进行ref转发。
然后就是我们的代码规范,是用Function还是Class,共同的代码块如何抽象,如何,还有命名规范是什么,哪些属性必选,哪些属性可选,默认值是什么?我們是怎么考虑的?
所以最终的组件使用可能会是这种形式:
import Button from './componenet/Button'<Button htmlType="submit" aria-label="add" variant="contained" color="rgba(17, 82, 147, 1)" click="clickHandler" />添加</Button>
在我们开发的过程中,有一道麻烦但又必不可少的工序就是单元测试,这时候单元测试的库我们是怎么选?用Jest还是Mocha?测试用例怎么写?如何模拟点击或者异步响应?是否需要快照(snapshots)?这也是在我们的考虑范围内。
所以我们的测试脚本可能长这样:
import Button from './componenet/Button'import { shallow } from 'enzyme'describe('<Button />', ()=> { it('render success', ()=> { const wrapper=shallow(( <Button htmlType="submit" aria-label="add" variant="contained" color="rgba(17, 82, 147, 1)" click="clickHandler" />添加</Button> )) expect(wrapper.text('添加')).to.equal(true) })})
其它的诸如开发文档,使用文档,版本迭代,项目配置,打包开发优化,以及其他自动化的功能,也是我们所需要考虑。
以上便是我们在开发一个“按钮(<Button>)组件”时可能会考虑到的点,可能有不够完善的地方,但是我想说的意思是,这其实可以很好的衡量一个人的JavaScript水平。比如你再会手写原型链关系图,闭包,防抖,节流等基础概念,但是如果不在项目中运用起来,终究是纸上谈兵,对技术水平没有太多实质的帮助,当然不是说精通这些内容不好,但是比起实战,还是差强人意。
能手写代码的不一定是高级,但是如果能写好一个组件,水平再差也不会差到哪里去。
本文似乎有点文不对题了,本来谈的是“如何衡量一个人的JavaScript水平”,结果却超纲了许多。但是通过这种方式,确实能够判断出一个人代码水平,当然也并不只是JS,换成安卓,IOS也同样适用。
不知道你是通过什么方式来衡量一个的JavaScript水平的呢?欢迎留言区域回复互动。
者|颜海镜
编辑|覃云
出处丨前端之巅
本文已获作者授权,转载来源:
https://segmentfault.com/a/1190000016389031
划重点,这是一道面试必考题,很多面试官都喜欢问这个问题,我就被问过好几次了。
要实现上图的效果看似很简单,实则暗藏玄机,本文总结了一下 CSS 实现水平垂直居中的方式大概有下面这些,本文将逐一介绍一下,我将本文整理成了一个 github 仓库在:https://github.com/yanhaijing/vertical-center
欢迎大家 star。
仅居中元素定宽高适用:
居中元素不定宽高:
为了实现上面的效果先来做些准备工作,假设 HTML 代码如下,总共两个元素,父元素和子元素:
<div class="wp"> <div class="box size">123123</div> </div>
wp 是父元素的类名,box 是子元素的类名,因为有定宽和不定宽的区别,size 用来表示指定宽度,下面是所有效果都要用到的公共代码,主要是设置颜色和宽高。
注意:后面不在重复这段公共代码,只会给出相应提示。
/* 公共代码 */ .wp { border: 1px solid red; width: 300px; height: 300px; } .box { background: green; } .box.size{ width: 100px; height: 100px; } /* 公共代码 */
绝对定位的百分比是相对于父元素的宽高,通过这个特性可以让子元素的居中显示,但绝对定位是基于子元素的左上角,期望的效果是子元素的中心居中显示。
为了修正这个问题,可以借助外边距的负值,负的外边距可以让元素向相反方向定位,通过指定子元素的外边距为子元素宽度一半的负值,就可以让子元素居中了,css 代码如下。
/* 此处引用上面的公共代码 */ /* 此处引用上面的公共代码 */ /* 定位代码 */ .wp { position: relative; } .box { position: absolute;; top: 50%; left: 50%; margin-left: -50px; margin-top: -50px; }
这是我比较常用的方式,这种方式比较好理解,兼容性也很好,缺点是需要知道子元素的宽高。
点击查看完整 DEMO:
http://yanhaijing.com/vertical-center/absolute1.html
这种方式也要求居中元素的宽高必须固定,HTML 代码如下:
<div class="wp"> <div class="box size">123123</div> </div>
这种方式通过设置各个方向的距离都是 0,此时再讲 margin 设为 auto,就可以在各个方向上居中了。
/* 此处引用上面的公共代码 */ /* 此处引用上面的公共代码 */ /* 定位代码 */ .wp { position: relative; } .box { position: absolute;; top: 0; left: 0; right: 0; bottom: 0; margin: auto; }
这种方法兼容性也很好,缺点是需要知道子元素的宽高。
点击查看完整 DEMO:
http://yanhaijing.com/vertical-center/absolute2.html
这种方式也要求居中元素的宽高必须固定,所以我们为 box 增加 size 类,HTML 代码如下:
<div class="wp"> <div class="box size">123123</div> </div>
感谢 css3 带来了计算属性,既然 top 的百分比是基于元素的左上角,那么在减去宽度的一半就好了,代码如下
/* 此处引用上面的公共代码 */ /* 此处引用上面的公共代码 */ /* 定位代码 */ .wp { position: relative; } .box { position: absolute;; top: calc(50% - 50px); left: calc(50% - 50px); }
这种方法兼容性依赖 calc 的兼容性,缺点是需要知道子元素的宽高。
点击查看完整 DEMO:
http://yanhaijing.com/vertical-center/absolute3.html
还是绝对定位,但这个方法不需要子元素固定宽高,所以不再需要 size 类了,HTML 代码如下:
<div class="wp"> <div class="box">123123</div> </div>
修复绝对定位的问题,还可以使用 css3 新增的 transform,transform 的 translate 属性也可以设置百分比,其是相对于自身的宽和高,所以可以讲 translate 设置为 -50%,就可以做到居中了,代码如下:
/* 此处引用上面的公共代码 */ /* 此处引用上面的公共代码 */ /* 定位代码 */ .wp { position: relative; } .box { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
这种方法兼容性依赖 translate2d 的兼容性。
点击查看完整 DEMO:
http://yanhaijing.com/vertical-center/absolute4.html
利用行内元素居中属性也可以做到水平垂直居中,HTML 代码如下:
<div class="wp"> <div class="box">123123</div> </div>
把 box 设置为行内元素,通过 text-align 就可以做到水平居中,但很多同学可能不知道通过通过 vertical-align 也可以在垂直方向做到居中,代码如下:
/* 此处引用上面的公共代码 */ /* 此处引用上面的公共代码 */ /* 定位代码 */ .wp { line-height: 300px; text-align: center; font-size: 0px; } .box { font-size: 16px; display: inline-block; vertical-align: middle; line-height: initial; text-align: left; /* 修正文字 */ }
这种方法需要在子元素中将文字显示重置为想要的效果。
点击查看完整 DEMO:
http://yanhaijing.com/vertical-center/lineheight.html
很多同学一定和我一样不知道 writing-mode 属性,感谢 @张鑫旭老师的反馈,简单来说 writing-mode 可以改变文字的显示方向,比如可以通过 writing-mode 让文字的显示变为垂直方向。
<div class="div1">水平方向</div> <div class="div2">垂直方向</div> .div2 { writing-mode: vertical-lr; }
显示效果如下:
水平方向 垂 直 方 向
更神奇的是所有水平方向上的 css 属性,都会变为垂直方向上的属性,比如 text-align,通过 writing-mode 和 text-align 就可以做到水平和垂直方向的居中了,只不过要稍微麻烦一点:
<div class="wp"> <div class="wp-inner"> <div class="box">123123</div> </div> </div> /* 此处引用上面的公共代码 */ /* 此处引用上面的公共代码 */ /* 定位代码 */ .wp { writing-mode: vertical-lr; text-align: center; } .wp-inner { writing-mode: horizontal-tb; display: inline-block; text-align: center; width: 100%; } .box { display: inline-block; margin: auto; text-align: left; }
这种方法实现起来和理解起来都稍微有些复杂。
点击查看完整 DEMO:
http://yanhaijing.com/vertical-center/writing-mode.html
曾经 table 被用来做页面布局,现在没人这么做了,但 table 也能够实现水平垂直居中,但是会增加很多冗余代码:
<table> <tbody> <tr> <td class="wp"> <div class="box">123123</div> </td> </tr> </tbody> </table>
tabel 单元格中的内容天然就是垂直居中的,只要添加一个水平居中属性就好了。
.wp { text-align: center; } .box { display: inline-block; }
这种方法就是代码太冗余,而且也不是 table 的正确用法。
点击查看完整 DEMO:
http://yanhaijing.com/vertical-center/table.html
css 新增的 table 属性,可以让我们把普通元素,变为 table 元素的现实效果,通过这个特性也可以实现水平垂直居中。
<div class="wp"> <div class="box">123123</div> </div>
下面通过 css 属性,可以让 div 显示的和 table 一样:
.wp { display: table-cell; text-align: center; vertical-align: middle; } .box { display: inline-block; }
这种方法和 table 一样的原理,但却没有那么多冗余代码,兼容性也还不错。
点击查看完整 DEMO:
http://yanhaijing.com/vertical-center/css-table.html
flex 作为现代的布局方案,颠覆了过去的经验,只需几行代码就可以优雅的做到水平垂直居中。
<div class="wp"> <div class="box">123123</div> </div> .wp { display: flex; justify-content: center; align-items: center; }
目前在移动端已经完全可以使用 flex 了,PC 端需要看自己业务的兼容性情况。
点击查看完整 DEMO:
http://yanhaijing.com/vertical-center/flex.html
感谢 @一丝姐 反馈的这个方案,css 新出的网格布局,由于兼容性不太好,一直没太关注,通过 grid 也可以实现水平垂直居中。
<div class="wp"> <div class="box">123123</div> </div> .wp { display: grid; } .box { align-self: center; justify-self: center; }
代码量也很少,但兼容性不如 flex,不推荐使用。
点击查看完整 DEMO:
http://yanhaijing.com/vertical-center/grid.html
下面对比下各个方式的优缺点,肯定又双叒叕该有同学说回字的写法了,简单总结下:
小贴士:关于 flex 的兼容性决方案,请看这里:
https://yanhaijing.com/css/2016/08/21/flex-practice-on-mobile/
最近发现很多同学都对 css 不够重视,这其实是不正确的,比如下面的这么简单的问题都有那么多同学不会,我也是很无语:
<div class="red blue">123</div> <div class="blue red">123</div> .red { color: red } .blue { color: blue }
问两个 div 的颜色分别是什么,竟然只有 40% 的同学能够答对,这 40% 中还有很多同学不知道为什么,希望这些同学好好补习下 CSS 基础。
备JavaScript面试时应了解的事项。
JavaScript现在是一种非常流行的编程语言,基于该语言,派生了大量库和框架。 但是,无论高层生态系统如何发展,离不开原始的JavaScript。 在这里,我选择了4个JavaScript面试问题来测试程序员使用普通JavaScript的技能。
如何手动实现Array.prototype.map方法?
熟练使用数组的内置方法并不难。但是,如果您只是熟悉语法而又不了解原理,那么很难真正理解JavaScript。
对于Array.prototype.map,它将创建一个新数组,其中将填充在调用数组中每个元素上调用提供的函数的结果。
如果引用lodash,我们可以编写一个map函数,如下所示:
function map(array, iteratee) {
let index=-1
const length=array==null ? 0 : array.length
const result=new Array(length)
while (++index < length) {
result[index]=iteratee(array[index], index, array)
}
return result
}
使用示例:
如何实现这种编码效果?
我们可以看到,当我们尝试连续打印obj.a三次时,会得到三种不同的结果。看起来多么不可思议!
您可以创建一个神秘的对象obj来实现此效果吗?
实际上,此问题有三种解决方案:
· 访问者属性
· Object.defineProperty
· 代理
根据ECMAScript,对象的属性可以采用两种形式:
从逻辑上讲,对象是属性的集合。每个属性都是数据属性或访问器属性:
· 数据属性将键值与ECMAScript语言值和一组布尔属性相关联。
· 访问器属性将键值与一个或两个访问器函数以及一组布尔属性相关联。访问器函数用于存储或检索与属性关联的ECMAScript语言值。
所谓的数据属性通常是我们写的:
let obj={ a: 1, b: 2}
我们对一个对象的属性只有两个操作:读取属性和设置属性。对于访问器属性,我们使用get和set方法定义属性,其编写方式如下:
let obj={
get a(){
console.log('triggle get a() method')
console.log('you can do anything as you want')
return 1
},
set a(value){
console.log('triggle set a() method')
console.log('you can do anything as you want')
console.log(`you are trying to assign ${value} to obj.a`)
}
}
访问属性为我们提供了强大的元编程能力,因此我们可以通过以下方式满足我们的要求:
let obj={
_initValue: 0,
get a() {
this._initValue++;
return this._initValue
}
}
console.log(obj.a, obj.a, obj.a)
第二种方法是使用Object.defineProperty,该方法的工作方式与我们用来访问属性的方法相同,除了不是直接声明访问属性,而是通过Object.defineProperty配置访问属性。
这使用起来更加灵活,因此我们可以这样编写:
let obj={}Object.defineProperty(obj, 'a', { get: (function(){ let initValue=0; return function(){ initValue++; return initValue } })()})console.log(obj.a, obj.a, obj.a)
在这里的get方法中,我们使用了一个闭包,以便我们需要使用的变量initValue隐藏在闭包中,并且不会污染其他范围。
第三种方法是使用代理。
使用代理,我们可以拦截对对象属性的访问。 只要我们使用代理来拦截对obj.a的访问,然后依次返回1、2和3,我们就可以在以下条件之前完成要求:
let initValue=0;
let obj=new Proxy({}, {
get: function(item, property, itemProxy){
if(property==='a'){
initValue++;
return initValue
}
return item[property]
}
})
console.log(obj.a, obj.a, obj.a)
为什么理解这个问题很重要?因为Object.defineProperty和Proxy给了我们强大的元编程能力,所以我们可以适当地修改对象以做一些特殊的事情。
在著名的前端框架Vue中,其核心机制之一是数据的双向绑定。在Vue2.0中,Vue通过使用Object.defineProperty实现了该机制。在Vue3.0中,使用Proxy完成此机制。
如果不掌握Vue之类的框架,您将无法真正理解。如果您掌握了这些原则,则只需学习Vue的一半,就可以获得两倍的结果。
运行此代码的结果是什么?
function foo(a,b) {
console.log(b)
return {
foo:function(c){
return foo(c,a);
}
};
}
let res=foo(0);
res.foo(1);
res.foo(2);
res.foo(3);
上面的代码同时具有多个嵌套函数和三个foo嵌套函数,乍一看看起来非常繁琐。那么,我们如何理解这一点呢?
首先,请确保上面的代码中有多少个功能?我们可以看到在上面的代码中的两个地方都使用了关键字函数,因此上面的代码中有两个函数,即第一行函数foo(a,b) 和第四行 foo:function(c)。并且这两个函数具有相同的名称。
第二个问题:第5行的foo(c,a)调用哪个函数?如果不确定,让我们来看一个简单的示例:
var obj={
fn:function (){
console.log(fn);
}
};
obj.fn()
如果我们运行该代码,是否会引发异常? 答案是肯定的。
这是因为obj.fn()方法的上限是全局的,并且无法访问obj内部的fn方法。
回到前面的示例,以同样的逻辑,当我们调用foo(c,a)时,实际上是在第一行上调用foo函数。
当我们调用res.foo(1)时,将调用哪个foo? 显然,第4行的foo函数被调用。
因为这两个foo函数的工作方式不同,所以我们可以将其中一个的名称更改为bar,以使我们更容易理解代码。
function foo(a,b) {
console.log(b)
return {
bar:function(c){
return foo(c,a);
}
};
}
let res=foo(0);
res.bar(1);
res.bar(2);
res.bar(3);
此更改不会影响最终结果,但会使我们更容易理解代码。如果将来遇到类似的问题,请尝试此技巧。
每次调用一个函数时,都会创建一个新的作用域,因此我们可以绘制图表以帮助我们理解代码工作原理的逻辑。
当我们执行let res=foo(0);时,实际上是在执行foo(0,undefiend)。此时,将在程序中创建一个新的作用域,在当前作用域中a=0,b=undefined。因此,我绘制的图看起来像这样。
然后将执行console.log(b),因此它第一次在控制台中打印出" undefined"。
然后执行res.bar(1),创建一个新范围,其中c=1:
然后从上面的函数中再次调用foo(c,a),它实际上是foo(1,0),作用域如下所示:
在新作用域中,a的值为1,b的值为0,因此控制台将打印出0。
再次执行res.bar(2)。注意,res.bar(2)和res.bar(1)是并行关系,因此我们应该像这样绘制范围图:
因此,在此代码中,控制台也会打印出值0。
执行res.bar(3)的过程也是如此,控制台仍显示0。
因此,以上代码的最终结果是:
实际上,上述问题可以用其他方式改变。例如,可以将其更改为以下内容:
function foo(a,b) {
console.log(b)
return {
foo:function(c){
return foo(c,a);
}
};
}
foo(0).foo(1).foo(2).foo(3);
在解决这个问题之前,我们要做的第一件事是区分两个不同的foo函数,因此可以将上面的代码更改为如下所示:
function foo(a,b) {
console.log(b)
return {
bar:function(c){
return foo(c,a);
}
};
}
foo(0).bar(1).bar(2).bar(3);
执行foo(0)时,作用域与以前相同,然后控制台将打印出" undefined"。
然后执行.bar(1)创建一个新的作用域。此参数1实际上是c的值。
然后.bar(1)方法再次调用foo(c,a),它实际上是foo(1,0)。这里的参数1实际上将是新作用域中a的值,而0将是新作用域中b的值。
因此,控制台随后输出了b的值,即0。
再次调用.bar(2),在新作用域中c的值为2:
然后.bar(2)调用foo(c,a),它实际上是foo(2,1),其中2是新作用域中a的值,而1是新作用域中b的值。
因此,控制台随后输出了b的值,即0。
然后它将执行.bar(3),该过程与之前相同,因此我将不扩展其描述,此步骤控制台将打印出2。
如上所述,代码运行的最终结果是:
好了,经过漫长的旅程,我们终于得到了答案。 这个问题很好地检验了受访者对封闭和范围的理解。
假设我们有一个看起来像这样的函数:
function compose (middleware) { // some code}
compose函数接受函数数组中间件:
let middleware=[]
middleware.push((next)=> {
console.log(1)
next()
console.log(1.1)
})
middleware.push((next)=> {
console.log(2)
next()
console.log(2.1)
})
middleware.push(()=> {
console.log(3)
})
let fn=compose(middleware)
fn()
当我们尝试执行fn时,它将调用中间件中的函数,并将下一个函数作为参数传递给每个小函数。
如果我们在一个小函数中执行next,则将调用中间件中该函数的next函数。而且,如果您接下来不执行,程序也不会崩溃。
执行完上面的代码后,我们得到以下结果:
1232.11.1
那么,我们如何编写一个compose函数来做到这一点呢?
首先,compose函数必须返回一个composed函数,因此我们可以编写如下代码:
function compose (middleware) {
return function () { }
}
然后,在返回的函数中,中间件的第一个函数开始执行。我们还将传递下一个函数作为其参数。所以让我们这样写:
function compose (middleware) {
return function () {
let f1=middleware[0]
f1(function next(){ })
}
}
下一个功能充当继续在中间件中运行的开关,如下所示:
function compose (middleware) {
return function () {
let f1=middleware[0]
f1(function next(){
let f2=middleware[1]
f2(function next(){ ... })
})
}
}
然后继续在下一个函数中调用第三个函数…等待,这看起来像递归! 因此,我们可以编写一个递归函数来完成此嵌套调用:
function compose (middleware) {
return function () {
dispatch(0)
function dispatch (i) {
const fn=middleware[i]
if (!fn) return null
fn(function next () {
dispatch(i + 1)
})
}
}
}
好的,这就是我们的撰写功能,所以让我们对其进行测试:
好吧,此功能完全可以完成其所需的工作。 但是我们也可以优化我们的compose函数可以支持异步函数。 我们可以改进以下代码:
function compose (middleware) {
return async function () {
await dispatch(0)
function async dispatch (i) {
const fn=middleware[i]
if (!fn)
return null
await fn(function next () {
dispatch(i + 1)
})
}
}
}
实际上,以上的撰写功能是众所周知的节点框架koa的核心机制。
当我选择候选人时,我接受他/她对某些框架不熟悉。毕竟,JavaScript生态系统中有太多的库和框架,没有人能完全掌握它们。但是我确实希望候选人知道这些重要的原始JavaScript技巧,因为它们是所有库和框架的基础。
实际上,我的草稿中还有其他一些面试问题,但由于本文篇幅有限,因此在此不再继续解释。稍后再与您分享。
本文主要涉及普通JavaScript,而不涉及浏览器,节点,框架,算法,设计模式等。如果您对这些主题也感兴趣,请随时发表评论。
感谢您的阅读!
(本文由闻数起舞翻译自bitfish的文章《Improve Your JavaScript Level With These 4 Questions》,转载请注明出处,原文链接:https://medium.com/javascript-in-plain-english/i-use-these-4-questions-to-find-outstanding-javascript-developers-4a468ea17155)
*请认真填写需求信息,我们会在24小时内与您取得联系。