只接受发布货源信息,不可发布违法信息,一旦发现永久封号,欢迎向我们举报!
1064879863
16货源网 > 餐饮行业新闻资讯 > 微信小程序开发 >  如何衡量一个人的 JavaScript 水平?


如何衡量一个人的 JavaScript 水平?

发布时间:2019-07-21 20:02:12  来源:网友自行发布(如侵权请联系本站立刻删除)  浏览:   【】【】【
如果能比较清晰地说出javascript对象机制和对函数对象的理解,基本上就不差。
如何衡量一个人的 JavaScript 水平?如果能比较清晰地说出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有一个地方最令我着迷:

  1. 函数式编程风格

  2. 异步编程风格

  3. 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 }

代码中,xy都是构造函数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个参数之后获取add1add10函数,这样使用起来非常灵活。

8. apply, call与bind方法

JavaScript开发者有必要理解applycallbind方法的不同点。它们的共同点是第一个参数都是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,find1find2啊!这里的关键在于old属性。

addMethod函数的调用顺序可知,people.find最终绑定的是find2函数。然而,在绑定find2时,oldfind1;同理,绑定find1时,oldfind0。3个函数find0,find1find2就这样通过闭包链接起来了。

根据addMethod的逻辑,当f.lengtharguments.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,确定没有挖坟。

下面是具体的反对内容:

  1. 立即执行函数

在 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生态有好奇心,不仅仅是为了一份工作...

责任编辑:
热门阅读排行
© 16货源网 1064879863