如何衡量一个人的 JavaScript 水平?
@董超 给出的链接只是很基础的javascript语法考察(当然题目的确出得很棒),刷完一本《javascript高级程序设计》的入门者都可以秒答。
仅仅从语法上来考察javascript水平是不合适的,因为javascript和其他语言不一样的最重要一点就是依赖于宿主,光会语法什么都写不出,所以必须考察一些其他的要点。
以下是我为了准备14年9月面试阿里所做的笔记节选:(仅仅是考察点,具体题目和扩展请自己思考)
基础
1,什么是命名空间,变量污染,变量声明提升,预编译?如何检查一段代码执行后是否声明了全局变量?
2,隐式转换的种种规则
3,关于对象,对象的属性可能有哪些特性(attribute)?什么是原型链?如何检测某一属性是在对象中还是原型链中?如何检测是否是普通对象或空对象?写一个工厂模式?(工厂模式也有很多细节,具体可以参考《javascript模式》一书,不是《javascript设计模式》)
4,变量类型检测、特性检测以及异常避免
5,setTimeout的特性
6,数组几个API的手工实现
7,事件
8,正则表达式,至少要明白$1和1的意思和几个API的用法。
9,javascript如何获得随机unicode字符?
10,JSON.stringify的参数传一个什么对象的时候不能处理?(可以试试JSON.stringify(window))
11,fn.apply的第一个参数是context(函数运行的上下文),那么这个参数传一个数组会怎么处理?
DOM操作
1,实现一些API如getElementsByClass(有很多细节,参见司徒正美的博客)
2,各种宽度高度(clientHeight之类)的含义及各浏览器之间的差异性
3,实现一个带回调函数的插入DOM节点API(须考虑script标签、documentFragment)
CSS操作
1,如何操作CSS的伪类和伪元素?
2,如何检测改浏览器是否支持某个CSS3的特性?
ajax操作
1,get和post的区别及使用要点
2,http协议,包括格式、状态码,cookie操作
3,跨域
4,转换JSON的几种方式,JSON.stringfy有什么使用限制?
性能
1,javascript有哪些性能优化的措施?可看《高性能javascript》
综合:各种插件的设计思路
其他的html5相关和NodeJS相关暂略
http://www.cnblogs.com/eric6/archive/2011/03/23/1991777.html谢邀。我觉得一个人的js牛逼有三个条件:基础扎实这就不说了,能否写出高性能的js代码,能否写出高性能的异步代码。基础扎实就不用说了,我觉得衡量一个人的水平,是会不会有一定的抽象能力,把常用的功能抽象为各种组件,不断优化。js还需要延伸很多周围的知识体系,浏览器就不用说,CSS,HTML,http协议等。
代码的可读性,可维护性,维护成本是很多人忽略的问题。对我来说不做无谓的设计,代码好懂一些,少埋些坑就很好了,因为做产品的话前端的东西总是得一直的改,容易改才能更快的让用户受益。我觉得主要还是看这个人写过什么。拿出一段代码来,仔仔细细读上一读。再让对方解释一下。想想性能,扩展性,如何更好的抽象化。而代码例子又不能仅限于web前端,至少包括浏览器端,nodejs,客户端,自动化工具,这四个方面。
看代码是了解这个人的习惯,基础,思路。
聊在于了解这个人的知识面,可塑性,以及接受能力。
我最反感笔试,基础概念的死抠,规范的死记硬背。这些不是没用,但仅仅是些锦上添花。
再或者在一定的时间内找一段500行左右的开源代码,让其review,再解释一下。谈谈体会和意见。我觉得更接地气一些。谢谢邀请,我只想从我对javascript的感受来说明我的观点:
javascript与我
看到标题,前端同学估计会抽我了,因我本是一个PHP程序员。
不过我对js是绝对的热爱,甚至超过PHP。就如我这个烂博客,后端主要是Nodejs+Redis构建的。
经过许许多多项目的洗礼,发现js有一个地方最令我着迷:
函数式编程风格
异步编程风格
OO风格
值得一提的是匿名函数,它犹如整个上下文环境中的眼睛,通过它可以看到一切。然而它又是那么谦卑,甚至连名字都没有。它就是js里的活雷锋,随叫随到,而且每次,它都是新的面孔且保证只被执行一次,它满怀激情的来,在你欢呼声中消失的无影无踪...
channel.on('fired', function(){...});
异步,是的,异步。你没法估计它什么时候到来,但却清楚的知道它一定会...要么成功要么失败。你只能事先为它安排好一切,然后,去喝杯茶吧。活雷锋简直是它的绝配,天生一对。让人好生艳羡...那如果创建了好多异步任务,它们会不会让引擎大哥忙得不可开交啊?呵呵,引擎大哥可聪明了:来得早不如来得巧,俺同一时间只处理一个事,来晚了排队吧。老大哥虽然耍了一点小聪明,但它做事却一丝不苟,不会有丝毫遗漏,异常专注。什么聪明天赋都是骗人的,专注,才是最大的财富。静下心来,整个世界都是灵动的。“潭中鱼可百许头,皆若空游无所依”说的就是“入微”的境界。
再看看js里的任督二脉:原型链和作用域链。无法直接观察到但却真实存在着。当你访问一个对象的属性,在使用“.”或者“[]”时,有没有觉得这其实是一个顺藤摸瓜的活儿...千里姻缘一线牵,若无缘,便异常终止吧,自挂东南枝。然而造化弄人,你找到的,不一定是你最爱的。姻缘一线牵,并没有说牵的只是你和她,在你和她之间,说不定还牵着别人。而能找到的,却是离你最近的。所以,异地恋是多么的脆弱与可怕...
可以说,人永远都只活在现在,当前,此刻。永远也无法逃离此刻,不管逃到哪里,它都如影随形跟着你,它就是“this”。只好仰头望天,长叹一声:“再也回不去了...”
亲,原谅我这小疯子的疯言疯语吧...晚安。
------------
摘自我的博客31楼347 博客详情页
1.能否解决问题。2.深入理解JavaScript系列
3.能否自己“创造”东西。相对于性能来说,对于前端的js,我觉得哪怕一个人能把所有的代码写到O(n)的效率,也比不得写一手好维护的代码。
命名明确吗?代码复用性高吗?
这种在任何语言里都是基础的问题,就是js里最重要的问题。
还有硬编码。这个一定要单独说。
每次我打开别人的js看到一堆红字(和html对应的字符串参数比如"class_name")就想死。用一个变量来代表这个可以省去很多HTML变动带来的麻烦。我甚至会用var htmlAttrs.classes.THE_CLASS这样的方式来把这些对应html元素的字符串放在一起维护。
说到这个的话,用一个好的IDE,利用好reference来跨文件完成自动提示,也是非常重要也是非常值得做的。
再然后就是全局变量和全局函数的问题。一个页面的windows对象下面有一堆非必要的全局函数也是会把人逼疯的。
js太过自由,所以coder的编码习惯显得特别重要。
相对而言,我觉得在大部分场景下,js的本地执行性能如果都成为影响用户体验的瓶颈,那绝对是产品的设计出问题了。前端工程师抠优化应该是在网络请求方面下功夫。
不是说前端js就不需要性能。只是说,把性能置于可维护性之上,或者为了性能而降低可维护性,是不可取的
举个例子,对于字符串拼接来说,单次少量的字符串拼接,[string1,string2,string3].join()比起string1+string2+string3来,如果考虑到团队里有不是很熟悉js的同事的话,我就会选用后者。
而对于js本身而言,最伟大的水平,就是对猿友善:code-monkey-friendly
一般是两个大方向来衡量水平:
第一种是对开发生态的熟悉程度和业务逻辑的熟悉程度,这类开发者可能对底层原理了解并不算深入,但是对于使用的框架的特性,哪些第三方插件好用,应该怎么用,什么场景应该怎么设计业务逻辑和基本代码架构非常清楚,而且经验丰富,也就是“反正这么写代码就能跑,效率也不错,至于为什么,我不知道也不care”,这是第一类牛人;
第二种就是传统意义上的牛人,跟所有其他行业都一样,所有领域最顶级的比拼拼的都是基本功,javascript也一样,这种人对原理了解非常深入,不仅仅是javascript的基础知识和原理,也包括框架的原理,甚至js解释器编译器的原理或源码。虽然其知识储备可能在大部分情况下看起来没有第一种人牛,但往往可能在技术瓶颈出现时发挥作用。
总结起来,js水平高的人大致可以分为两种,第一种,能把前端项目从0搞到90分的人;第二种,能把前端项目从90搞到不断接近满分的人。
更多精彩内容,请滑至顶部点击右上角关注小宅哦~
要不做做题!!啊哈哈、
Ready,Go !!
之前发一套很经典(lao)的JavaScript题了,大家评论都说有点偏,是奇技淫巧。哈哈哈哈,那我就把原答案回复JavaScript 有什么奇技淫巧?现在我准备认认真真总结44道题来回答这个问题。不过出题才发现还是挺难的,目前总结 了16题,先发出来了,后边我慢慢加。
这些题中有大部分是根据frontend-master上的js课总结的,最后还是推荐《你不知道JavaSript》作者Kyle Simpson的课(deep-javascript-v3)很多大佬都推荐他的书,我觉得他的课对我JavaScript水平提高有很大帮助。
1. 词法作用域
var teacher = "17dian";
function ask(question){
console.log(teacher,question);
}
function other(){
var teacher = "dongyang";
ask("who");
}
other()
答案: 17dian who
解析:(1)JavaScript在运行是分为两个步骤,先编译后执行
(2)编译会根据所有正式声明生成词法作用域,正式声明包括标识符(var、let等),函数参数。
(3)运行时会根据词法作用域引用变量
一句话:函数中变量是在定义时决定的,不是在运行时
2. 词法作用域
var teacher = "17dian";
function otherClass(teacher){
if(typeof teacher === 'undefined'){
console.log(teacher)
let teacher = "dongyang"
}else {
console.log(teacher);
}
}
otherClass()
答案: ReferenceError: teacher is not defined
解析:(1)JavaScript在运行是分为两个步骤,先编译后执行
(2)编译会根据所有正式声明生成词法作用域,正式声明包括标识符(var、let等),函数参数。所以typeof的teacher在otherClass的作用域中
(3)块级作用域变量只作用于{}中,不允许未声明就使用
3. 函数声明和函数表达式
function fn(){
return print()
function print(){
console.log('1')
}
}
let fn2 = function fn(){
console.log(fn)
return print2()
var print2 = function(){
console.log('2')
}
}
fn()
fn2()
答案: 1 , fn函数 , print2 is not a function
解析:(1)函数声明会加入变量放入临近的作用域中,而函数表达式会将变量放入自己的作用域中
(2)函数声明的变量提升是可以直接调用函数的,而print2变量提升此时被初始化为undefined
4. 闭包
for(let i=1;i<=3;i++){
setTimeout(function(){
console.log(i)
}, i*1000)
}
for(var i=1;i<=3;i++){
setTimeout(function(){
console.log(i)
}, i*1000)
}
答案: 1 , 2 , 3
4 , 4 , 4
解析: 闭包是指函数在执行时会根据其词法作用域去引用变量(在定义时决定的,不是在运行时)
所以在循环中中let中i会在不同作用域中
5.闭包的应用-模块化
var workshop = (function Module(){
let teacher = "17dian";
let publicAPI = {ask}
return publicAPI;
function ask(question){
console.log(teacher,question)
}
})()
let teacher = "dongyang";
workshop.ask("are you OK?")
答案: 17dian are you OK?
解析:(1)通过立即执行函数可以返回一个对象
(2)对象中所使用的变量根据词法作用域决定,不受执行环境影响
(3)定义在函数内部的私有变量不会被外界改变
6. 动态作用域
var workshop = {
teacher:"17dian",
ask(){
console.log(this.teacher,this.question)
}
}
var teacher = "dongyang";
var question = "are you OK?"
workshop.ask()
答案: 17dian undefined
解析: (1)this 关键字指向调用它的执行环境
(2)在workshop中不存在question变量
(3)索引对象中不存在的变量时为undefined
7.绑定丢失
var workshop = {
teacher:"17dian",
ask(question){
console.log(this.teacher,question)
}
}
let teacher = "dongyang";
setTimeout(workshop.ask,100,"lost")
答案: undefined "lost"
解析:(1)setTimeout中回调函数被window调用
(2)ES6中let声明的全局变量不会与顶层对象进行绑定
(3)可以通过call、bind解决绑定丢失
巩固:
var workshop = {
teacher:"17dian",
ask(question){
console.log(this.teacher,question)
}
}
var teacher = "dongyang";
setTimeout(workshop.ask,100,"lost") //dongyang lost
setTimeout(workshop.ask.bind(workshop),100,"lost") //17dian lost
var fn = workshop.ask;
fn("ok") //dongyang ok
fn.call(workshop,"ok") //17dian ok
注意:
使用严格模式下"use strict",fn("ok")不会默认指向windows
但是在setTimeout中回调函数指向windows,即使是在严格模式下(参考MDN),但不包括箭头函数
8. 箭头函数
var workshop = {
teacher:"17dian",
ask(question){
setTimeout(function(){
console.log(this)
},100)
},
arrowAsk(question){
setTimeout(()=>{
console.log(this)
},100)
}
}
workshop.ask()
workshop.arrowAsk()
答案: Window
workshop
解析: 箭头函数类似于变量不定义this,而是引用所以词法环境中的this
这一点很重要,所以它会引用arrowAsk下的this,而不是setTimeout下的windows
通常也可以理解为指向父级作用域,但是不准确 [详情](https://frontendmasters.com/courses/deep-javascript-v3/)
9. new关键字
var teacher = "dongyang"
function fn(){
console.log(this.teacher)
this.teacher = "17dian"
console.log(this.teacher)
}
fn()
new fn()
答案: dongyang 17dian
undefined 17dian
解析: (1)fn()时this执向windows
(2)new fn()new关键字执行以后四个步骤
创建一个空对象、将this指向这个空对象、执行函数、返回对象
10. 原型
function WorkShop(teacher){
this.teacher = teacher;
}
WorkShop.prototype.ask = function(question){
console.log(this.teacher,question)
}
var objInstance = new WorkShop();
console.log(objInstance.__proto__ === WorkShop.prototype)
//第一问?
function AnoterWorkShop(teacher){
WorkShop.call(this,teacher)
}
AnoterWorkShop.prototype = new WorkShop()
var obj = new AnoterWorkShop("17dian");
obj.ask("hello")
//第二问?
AnoterWorkShop.prototype.ask = function(question){
console.log(question,this.teacher)
}
var obj2 = new AnoterWorkShop("dongyang");
obj2.ask("hello")
obj.ask("hello")
//第三问?
console.log(obj instanceof AnoterWorkShop)
console.log(obj instanceof WorkShop)
console.log(obj instanceof Function)
答案: true
17dian hello
hello dongyang
hello 17dian
true true fales
解析: (1)函数中有原型对象的属性prototype
(2)通过new时创建的实例上对象中有__proto__指向原型对象
(3)所以在AnoterWorkShop的原型对象上通过new WorkShop()继承WorkShop上的属性
(4) 创建后对象可以通过instanceof判断构造函数
(5)注意函数的构造函数是Function而对象的构造函数是Object
11. 原型继承
function WorkShop(teacher){
this.teacher = teacher;
}
WorkShop.prototype.ask = function(question){
console.log(this.teacher,question)
}
function AnoterWorkShop(teacher){
WorkShop.call(this,teacher)
}
AnoterWorkShop.prototype = Object.create(WorkShop.prototype);
AnoterWorkShop.prototype.speadup = function(msg){
this.ask(msg.toUpperCase())
}
var obj = new AnoterWorkShop("17dian")
obj.speadup("are you ok?")
console.log(obj.hasOwnProperty("teacher"))
console.log(obj.hasOwnProperty("speadup"))
console.log(obj.hasOwnProperty("ask"))
答案: 17dian ARE YOU OK?
true false false
解析: (1)AnoterWorkShop中通过借用构造函数的方法使对象的属性不挂载到原型上
(2)通过Object.create()只继承原型链对象
(3)在AnoterWorkShop中添加子类方法
(4)可以通过hasOwnProperty判断是属性是否在原型链上
11. class语法糖
class WorkShop{
constructor(teacher){
this.teacher = teacher
}
ask(question){
console.log(this.teacher,question)
}
}
class AnoterWorkShop extends WorkShop{
constructor(teacher){
super(teacher)
this.anoterName = "sub"
}
speadup(question){
super.ask(question.toUpperCase())
}
}
var obj = new WorkShop("17dian");
var obj2 = new AnoterWorkShop("dongyang");
obj.ask("hello")
obj2.ask("hello")
obj2.speadup("hello")
console.log(obj2 instanceof AnoterWorkShop)
console.log(obj2 instanceof WorkShop)
console.log(obj2 instanceof Function)
答案: 17dian hello
dongyang hello
dongyang HELLO
true true false
解析: (1)通过这个例子可以看到class本质上就是一个语法糖写起来更简便
(2)super作为函数调用时,代表父类的构造函数
super作为对象时,指向父类的原型对象
12. 原型上的this绑定丢失
function WorkShop(teacher){
this.teacher = teacher;
}
WorkShop.prototype.ask = function(question){
console.log(this.teacher,question)
}
var objInstance = new WorkShop("17dian");
console.log(objInstance.hasOwnProperty("ask"))
setTimeout(objInstance.ask, 100,"111")
答案: false
undefined "111"
解析: setTimeout回调函数中仍然存在绑定丢失问题
注意setTimeout异步哦
13. 原型上的this绑定丢失解决
function WorkShop(teacher){
this.teacher = teacher;
this.ask = this.ask.bind(this)
}
WorkShop.prototype.ask = function(question){
console.log(this.teacher,question)
}
var objInstance = new WorkShop("17dian");
console.log(objInstance.hasOwnProperty("ask"))
setTimeout(objInstance.ask, 100,"111")
答案: true
17dian 111
解析: 在WorkShop构造函数属性中重新绑定ask并用bind指定执行的环境
14. 原型上的this绑定丢失解决
class WorkShop{
constructor(teacher){
this.teacher = teacher
}
ask(){
console.log("输出:"+" "+this.teacher)
}
}
class AnoterWorkShop extends WorkShop{
constructor(teacher){
super(teacher)
}
speadup(){
setTimeout(this.ask.bind(this),100)
setTimeout(()=>{ this.ask()},200)
setTimeout(this.ask,300)
}
}
let obj = new AnoterWorkShop("17dian");
obj.speadup()
答案: 输出: 17dian
输出: 17dian
输出: undefined
解析: 绑定丢失问题可以通过箭头函数和bind来进行避免
15. 异步-宏任务与微任务
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
答案: promise1 promise2 setTimeout
解析: 事件循环是指JS通过事件循环去处理异步事件
宏任务是指异步事件(依靠浏览器实现的),比如点击事件、异步请求、定时器
微任务是指Promise等js引擎实现的
事件循环的机制是先执行函数,然后处理微任务,然后在执行下一个事件(就是宏任务)
《你不知道的JS》对这部分有详细介绍
16. 异步-宏任务与微任务
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');
function onClick1(e) {
console.log('inner')
setTimeout(function() {
console.log('timeout1');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
});
}
function onClick2(e) {
console.log('outer')
setTimeout(function() {
console.log('timeout2');
}, 0);
Promise.resolve().then(function() {
console.log('promise2');
});
}
setTimeout(function() { console.log('setTimeout');}, 0);
inner.addEventListener('click', onClick1);
outer.addEventListener('click', onClick2);
inner.click();
答案: inner outer promise1 promise2 setTimeout timeout1 timeout2
解析: 先执行顺序执行代码,然后根据点击事件先执行onClick1,然后执行onClick2
Promise是微任务,输出promise1 promise2
当代码执行完微任务是在依次执行宏任务输出 setTimeout timeout1 timeout2
详情请看Chrome开发者写的这篇文章https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
后边的我慢慢总结哈~~~~~保质保量的加到44道、、、
能够读懂这篇博客,尤其是最后1个,JS水平不会太差...
https://blog.fundebug.com/2017/07/17/10-javascript-difficulties/- 原文: 10 JavaScript concepts every Node.js programmer must master
- 译者: Fundebug
本文采用意译,而非直译。
1. 立即执行函数
立即执行函数,即Immediately Invoked Function Expression (IIFE),正如它的名字,就是创建函数的同时立即执行。它没有绑定任何事件,也无需等待任何异步操作:
(function() {
// 代码
})();
function(){…}是一个匿名函数,包围它的一对括号将其转换为一个表达式,紧跟其后的一对括号调用了这个函数。立即执行函数也可以理解为立即调用一个匿名函数。立即执行函数最常见的应用场景就是:将var变量的作用域限制于你们函数内,这样可以避免命名冲突。
2. 闭包
对于闭包(closure),当外部函数返回之后,内部函数依然可以访问外部函数的变量。
function f1() {
var N = 0; // N是f1函数的局部变量
function f2() {
// f2是f1函数的内部函数,是闭包
N += 1; // 内部函数f2中使用了外部函数f1中的变量N
console.log(N);
}
return f2;
}
var result = f1();
result(); // 输出1
result(); // 输出2
result(); // 输出3
代码中,外部函数f1只执行了一次,变量N设为0,并将内部函数f2赋值给了变量result。由于外部函数f1已经执行完毕,其内部变量N应该在内存中被清除,然而事实并不是这样:我们每次调用result的时候,发现变量N一直在内存中,并且在累加。为什么呢?这就是闭包的神奇之处了!
3. 使用闭包定义私有变量
通常,JavaScript开发者使用下划线作为私有变量的前缀。但是实际上,这些变量依然可以被访问和修改,并非真正的私有变量。这时,使用闭包可以定义真正的私有变量:
function Product() {
var name;
this.setName = function(value) {
name = value;
};
this.getName = function() {
return name;
};
}
var p = new Product();
p.setName("Fundebug");
console.log(p.name); // 输出undefined
console.log(p.getName()); // 输出Fundebug
代码中,对象p的的name属性为私有属性,使用p.name不能直接访问。
4. prototype
每个JavaScript构造函数都有一个prototype属性,用于设置所有实例对象需要共享的属性和方法。prototype属性不能列举。JavaScript仅支持通过prototype属性进行继承属性和方法。
function Rectangle(x, y) {
this._length = x;
this._breadth = y;
}
Rectangle.prototype.getDimensions = function() {
return {
length: this._length,
breadth: this._breadth
};
};
var x = new Rectangle(3, 4);
var y = new Rectangle(4, 3);
console.log(x.getDimensions()); // { length: 3, breadth: 4 }
console.log(y.getDimensions()); // { length: 4, breadth: 3 }
代码中,x和y都是构造函数Rectangle创建的对象实例,它们通过prototype继承了getDimensions方法。
5. 模块化
JavaScript并非模块化编程语言,至少ES6落地之前都不是。然而对于一个复杂的Web应用,模块化编程是一个最基本的要求。这时,可以使用立即执行函数来实现模块化,正如很多JS库比如jQuery以及我们Fundebug都是这样实现的。
var module = (function() {
var N = 5;
function print(x) {
console.log("The result is: " + x);
}
function add(a) {
var x = a + N;
print(x);
}
return {
description: "This is description",
add: add
};
})();
console.log(module.description); // 输出"this is description"
module.add(5); // 输出“The result is: 10”
所谓模块化,就是根据需要控制模块内属性与方法的可访问性,即私有或者公开。在代码中,module为一个独立的模块,N为其私有属性,print为其私有方法,decription为其公有属性,add为其共有方法。
6. 变量提升
JavaScript会将所有变量和函数声明移动到它的作用域的最前面,这就是所谓的变量提升(Hoisting)。也就是说,无论你在什么地方声明变量和函数,解释器都会将它们移动到作用域的最前面。因此我们可以先使用变量和函数,而后声明它们。
但是,仅仅是变量声明被提升了,而变量赋值不会被提升。如果你不明白这一点,有时则会出错:
console.log(y); // 输出undefined
y = 2; // 初始化y
上面的代码等价于下面的代码:
var y; // 声明y
console.log(y); // 输出undefined
y = 2; // 初始化y
为了避免BUG,开发者应该在每个作用域开始时声明变量和函数。
7. 柯里化
柯里化,即Currying,可以是函数变得更加灵活。我们可以一次性传入多个参数调用它;也可以只传入一部分参数来调用它,让它返回一个函数去处理剩下的参数。
var add = function(x) {
return function(y) {
return x + y;
};
};
console.log(add(1)(1)); // 输出2
var add1 = add(1);
console.log(add1(1)); // 输出2
var add10 = add(10);
console.log(add10(1)); // 输出11
代码中,我们可以一次性传入2个1作为参数add(1)(1),也可以传入1个参数之后获取add1与add10函数,这样使用起来非常灵活。
8. apply, call与bind方法
JavaScript开发者有必要理解apply、call与bind方法的不同点。它们的共同点是第一个参数都是this,即函数运行时依赖的上下文。
三者之中,call方法是最简单的,它等价于指定this值调用函数:
var user = {
name: "Rahul Mhatre",
whatIsYourName: function() {
console.log(this.name);
}
};
user.whatIsYourName(); // 输出"Rahul Mhatre",
var user2 = {
name: "Neha Sampat"
};
user.whatIsYourName.call(user2); // 输出"Neha Sampat"
apply方法与call方法类似。两者唯一的不同点在于,apply方法使用数组指定参数,而call方法每个参数单独需要指定:
- apply(thisArg, [argsArray])
- call(thisArg, arg1, arg2, …)
var user = {
greet: "Hello!",
greetUser: function(userName) {
console.log(this.greet + " " + userName);
}
};
var greet1 = {
greet: "Hola"
};
user.greetUser.call(greet1, "Rahul"); // 输出"Hola Rahul"
user.greetUser.apply(greet1, ["Rahul"]); // 输出"Hola Rahul"
使用bind方法,可以为函数绑定this值,然后作为一个新的函数返回:
var user = {
greet: "Hello!",
greetUser: function(userName) {
console.log(this.greet + " " + userName);
}
};
var greetHola = user.greetUser.bind({greet: "Hola"});
var greetBonjour = user.greetUser.bind({greet: "Bonjour"});
greetHola("Rahul") // 输出"Hola Rahul"
greetBonjour("Rahul") // 输出"Bonjour Rahul"
9. Memoization
Memoization用于优化比较耗时的计算,通过将计算结果缓存到内存中,这样对于同样的输入值,下次只需要中内存中读取结果。
function memoizeFunction(func) {
var cache = {};
return function() {
var key = arguments[0];
if (cache[key]) {
return cache[key];
} else {
var val = func.apply(this, arguments);
cache[key] = val;
return val;
}
};
}
var fibonacci = memoizeFunction(function(n) {
return n === 0 || n === 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
});
console.log(fibonacci(100)); // 输出354224848179262000000
console.log(fibonacci(100)); // 输出354224848179262000000
代码中,第2次计算fibonacci(100)则只需要在内存中直接读取结果。
10. 函数重载
所谓函数重载(method overloading),就是函数名称一样,但是输入输出不一样。或者说,允许某个函数有各种不同输入,根据不同的输入,返回不同的结果。凭直觉,函数重载可以通过if…else或者switch实现,这就不去管它了。jQuery之父John Resig提出了一个非常巧(bian)妙(tai)的方法,利用了闭包。
从效果上来说,people对象的find方法允许3种不同的输入: 0个参数时,返回所有人名;1个参数时,根据firstName查找人名并返回;2个参数时,根据完整的名称查找人名并返回。
难点在于,people.find只能绑定一个函数,那它为何可以处理3种不同的输入呢?它不可能同时绑定3个函数find0,find1与find2啊!这里的关键在于old属性。
由addMethod函数的调用顺序可知,people.find最终绑定的是find2函数。然而,在绑定find2时,old为find1;同理,绑定find1时,old为find0。3个函数find0,find1与find2就这样通过闭包链接起来了。
根据addMethod的逻辑,当f.length与arguments.length不匹配时,就会去调用old,直到匹配为止。
function addMethod(object, name, f) {
var old = object[name];
object[name] = function() {
// f.length为函数定义时的参数个数
// arguments.length为函数调用时的参数个数
if (f.length === arguments.length) {
return f.apply(this, arguments);
} else if (typeof old === "function") {
return old.apply(this, arguments);
}
};
}
// 不传参数时,返回所有name
function find0() {
return this.names;
}
// 传一个参数时,返回firstName匹配的name
function find1(firstName) {
var result = [];
for (var i = 0; i < this.names.length; i++) {
if (this.names[i].indexOf(firstName) === 0) {
result.push(this.names[i]);
}
}
return result;
}
// 传两个参数时,返回firstName和lastName都匹配的name
function find2(firstName, lastName) {
var result = [];
for (var i = 0; i < this.names.length; i++) {
if (this.names[i] === firstName + " " + lastName) {
result.push(this.names[i]);
}
}
return result;
}
var people = {
names: ["Dean Edwards", "Alex Russell", "Dean Tom"]
};
addMethod(people, "find", find0);
addMethod(people, "find", find1);
addMethod(people, "find", find2);
console.log(people.find()); // 输出["Dean Edwards", "Alex Russell", "Dean Tom"]
console.log(people.find("Dean")); // 输出["Dean Edwards", "Dean Tom"]
console.log(people.find("Dean", "Edwards")); // 输出["Dean Edwards"]
参考链接
- 闭包 - MDN
- JavaScript闭包-块级作用域和私有变量
- Javascript继承机制的设计思想 - 阮一峰
- 变量提升 - MDN
- JS函数式编程指南
- 浅谈JavaScript函数重载
关于Fundebug
Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有Google、360、金山软件、百姓网等众多品牌企业。欢迎大家免费试用!
版权声明
转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/07/17/10-javascript-difficulties/
关于 @cyyssly 的回答,我写了一篇反驳:
Fundebug:聊聊我的第一篇10万+,同时反驳某些评论我是来实名反对 @Fundebug 的回答的。刚开始看到这个题目并没有回答的欲望,因为即便都是前端,但大家的工作内容不同,深入研究的领域不一样,其实没有什么客观标准可以衡量一个人的JavaScript 水平,是骡子是马只能拉出来溜溜。
但是在看到一篇充满低级错误的回答已经超过1K赞以后,我有点坐不住了。真的没有想到知乎的平均水平低到这种程度,有点刷新了我的认知。个人觉得哪怕只有两三年经验的前端,也应该能看出这个回答的问题在哪里。回答的例子里满屏的 var 就不提了,最主要的问题归结起来有2点:一是知识点陈旧过时,二是过于注重技巧而忽视工程化。总体而言,5年前这是一个好的回答,但在9102年的今天已经不合时宜。在发这个回答之前我特地确认了原回答的发布日期是 2019-04-20,确定没有挖坟。
下面是具体的反对内容:
- 立即执行函数
在 ES6 引入 class 和块级变量后,立即执行函数就已经没有什么意义了。无论是为了私有属性,还是解决闭包问题,都有更好更直观的替代方案,为什么不用呢?
2. 闭包
闭包仍然是 JS 中的基础概念,值得花点时间去理解,也是实现私有属性、构造函数和函数柯里化的基础。但是原回答的例子中使用闭包的方式目前已经明确地淘汰了,只要简单地把 var 换成 let 就可以解决问题。
3. prototype
prototype 和基于原型链的继承都已经是过时的概念,前端三大全家桶都提供了更好的继承和重用方法,支持多态和混合等高级特性,不建议直接操作 prototype。
4. 模块化
ES6 时代模块化不必再通过立即执行函数来实现,而是可以写在单独的 js 文件中,在页面或组件中import 后使用。
5. 变量提升
不要利用变量提升的特性,因为违反直觉导致可读性变差,应该使用块级变量来替代,所有的 var 都应该替换为 let 或者 const。
6. apply, call 与 bind
不要人为改变 this 的指向制造混乱,反而应该使用 that = this 明确保留当前上下文。在 ES6 的箭头函数下, apply 和 call 是无效的。如果你发现自己需要使用 apply, call 或者 bind,99%的情况下有更简单的替代解决方案。
7. memoization
如有必要,请使用 redis,或其他内存数据库。相信我,你自己管理内存不会比 redis 做得更好。原文那种简单的 get, set 一个变量的情况只会出现在 hello world 例子里,在实际工程中几乎不存在。
8. 函数重载
毫无意义的知识,把多个参数封装为对象再传入即可,简单直观得多。John Resig 值得尊敬,但 jquery 时代追求精巧的设计思想确实已经老旧了,不再适合当今 web app 越发复杂的现状。
最后总结一句话:代码是用来解决问题的,不是用来秀技巧的,越花巧的代码维护的代价越高。如果有一百种解决问题的方法,请使用最简单的一种。
我来从另一个层面来分析为何很多人看到一些JavaScript的面试提问有很多都回答不上来。
其实有很多时候不是你的水平低,就是因为你在学的时候偷懒不看官方文档。
我就是其中之一
一次偶然的机会,让我负责前段的架构工作。使我打开了JavaScript新世界的大门。
1,迭代
2,制定命名规则(如Css采取BEM命名规则)
3,JavaScript架构
4,CSS架构
5,单元测试
6,分支管理策略(最小程度的避免代码冲突)
7,eslint策略
8,做好pr
。。。。。还有很多
很大一部分人,都是在已有的架构中工作的,架构师会把好的可迭代的架构方式写入其中。你只是按照规范这么写你并不知道为什么要这么写。所以一定要有进取心。要想为什么要这么写,这么写有什么好处。
最后希望能对大家有所启发。一起学习一起进步。
最后一个重载就别拿出去说了吧。js严格意义上没有重载。声明两个相同的函数,即使参数不同,也都是只调用后声明的那一个。
我说一个你们不知道的
“为什么一个string明明不是对象,却可以直接调用方法?”
因为在js里面,就是所谓的“万物皆对象”,一个string再调用方法的时候,实际上的过程是这样的
var str = 'aaaaa';
var arr = str.split("");
// 实际的过程是下面这样
var str = 'aaaaa';
var xxx = new String(str);
var xxx2 = xxx.split('');
var arr = xxx2;
9102年了,其实看一个人的JavaScript水平,就是看他能不能利用Typescript来写出一个结构清晰,组合合理的项目。
做不到就说明还是应该好好继续学习,不要玩那些花架子。
个人觉得 分成几个层次
一、知其然
对所有js相关周边知识知道其是什么,并有所应用
二、知其所以然
不仅知道是什么,而且知道运行的内部原理
三、对知识的灵活运用
对于技术的 前世今生,诞生,演化有深刻的理解的前提下,对知道什么场景使用什么是最合理的方案
四、上升到哲学
对技术有一种谜一样的哲学思想
我来抢答一下。如果让部分人感觉受到了人身攻击,不好意思那就是针对你的。
不知道为何这种炫技的东西反而大行其道。如果你认为自己会写点所谓高仿的方法就是牛X那我只能说,傻子他妈给傻子开门--傻到家了。
如果说面试官是为了考验一个人的功底而去出了一些“蹩脚”的问题,这其实属于无奈之举。
相当于我问你,你知道京酱肉丝需要用到猪肉,甜酱来炒制,配以大葱豆腐皮,卷在一起吃进肚子里。
那么请问,在养猪的时候,猪饲料的配比是怎样的?你是否可以手写出其中最为重要的三种配料?豆腐皮的制作应该采取何种工序?如果我用左手把菜品送入口中,这时候会不会有不一样的味道?
有病么?我就是想吃一口菜,我需要去知道怎么养猪么?自然有更专业的饲养人员去做这些事情,我需要关注的是一张豆腐皮里需要卷几丝大葱,多少肉,怎么样的配比吃进去最美味。
如果你觉得作为一个食客,不理解养猪的技巧就算作是不合格,是一个辣鸡,那请当我没回答过这个问题。
看了很多回答,多数偏题了。
题主的问题的要点是“JavaScript”的水平,不是问你数据结构、算法、编译原理blabla的水平。
回答这个问题,就得紧扣这个要点。
一个隐含的要点,题主没写,我认为要回答好,必须加上:“现代”JavaScript的水平
JavaScript非常有活力,发展很快,现在已经成熟的ES6,和最初的ES3,很不相同。这个时候说要衡量JavaScript的水平,应该指现代JavaScript语言的水平。有一位高赞答主 @Fundebug 的回答,很多都是已经过时的东西,有其他答主已经批判了,不多说了。
一个原则:不要看奇巧淫技,要看对这门语言的全局的理解、对优点和缺点的认知、对生态的了解、在有问题以后、知道去哪里找答案的能力。
按照上面三个要点,我觉得应该这样衡量JavaScript的水平:
知道JavaScript ES3/ES5/ES6的优点、缺点,尤其是ES6+要解决的问题
JS ES3/ES5/ES6+的优点:函数是一等公民、原型继承、闭包
ES3/ES5的缺点:全局变量var、没有标准的package、this的绑定、强制类型转换、原型链继承比较麻烦、callback地狱
ES6+要解决问题的方法:let/const,箭头函数,解决this绑定丢失,import统一package,class让继承更简单
ES6+提供的一些增强的数据结构和异步处理:array、map、for、iterator、generator、yield、promise/async/await, destructor
对JavaScript与运行环境之间的关系有比较准确的认识
浏览器及DOM、Nodejs与Eventloop、小程序等各自的全局变量、package方式、加载方式、语言版本、如何将高版本JavaScript用在运行开发环境上
对JavaScript前端、后端生态有基本的认识
前端:React/Vue/Angular知道是什么,比较深入了解其中之一
后端:Node、Express、Koa、阿里的egg等
服务器端渲染:Nextjs...
对TypeScript的了解,可以作为加分项
最重要:好奇心,要对JavaScript生态有好奇心,不仅仅是为了一份工作...