1、立即执行函数
立即执行函数,即ImmediatelyInvokedFunctionExpression
(IIFE),正如它的名字,就是创建函数的同时立即执行。它没有绑定任何事件,也无需等待任何异步操作:
1.(function(){
2.
3.//代码
4.
5.//...
6.
7.})()
function(){}是一个匿名函数,包围它的一对括号将其转换为一个表达式,紧跟其后的一对括号调用了这个函数。立即执行函数也可以理解为立即调用一个匿名函数。立即执行函数最常见的应用场景就是:将var变量的作用域限制于你们函数内,这样可以避免命名冲突。
2、闭包
对于闭包(closure),当外部函数返回之后,内部函数依然可以访问外部函数的变量。
1.(function(){
2.
3.//代码
4.
5.//...
6.
7.})()
代码中,外部函数f1只执行了一次,变量N设为0,并将内部函数f2赋值给了变量result。由于外部函数f1已经执行完毕,其内部变量N应该在内存中被清除,然而事实并不是这样:我们每次调用result的时候,发现变量N一直在内存中,并且在累加。为什么呢?这就是闭包的神奇之处了!
3、使用闭包定义私有变量
通常,JavaScript开发者使用下划线作为私有变量的前缀。但是实际上,这些变量依然可以被访问和修改,并非真正的私有变量。这时,使用闭包可以定义真正的私有变量:
1.functionProduct(){
2.
3.varname
4.
5.this.setName=function(value){
6.name=value
7.}
8.
9.this.getName=function(){
10.returnname
11.}
12.}
13.
14.varp=newProduct()
15.p.setName("Fundebug")
16.
17.console.log(p.name)//输出undefined
18.console.log(p.getName())//输出Fundebug
代码中,对象p的的name属性为私有属性,使用p.name不能直接访问。
4、prototype
每个JavaScript构造函数都有一个prototype属性,用于设置所有实例对象需要共享的属性和方法。prototype属性不能列举。JavaScript仅支持通过prototype属性进行继承属性和方法。
1.functionRectangle(x,y)
2.{
3.this._length=x
4.this._breadth=y
5.}
6.
7.Rectangle.prototype.getDimensions=function()
8.{
9.return{
10.length:this._length,
11.breadth:this._breadth
12.}
13.}
14.
15.varx=newRectangle(3,4)
16.vary=newRectangle(4,3)
17.
18.console.log(x.getDimensions())//{length:3,breadth:4}
19.console.log(y.getDimensions())//{length:4,breadth:3}
代码中,x和y都是构造函数Rectangle创建的对象实例,它们通过prototype继承了getDimensions方法。
5、模块化
JavaScript并非模块化编程语言,至少ES6落地之前都不是。然而对于一个复杂的Web应用,模块化编程是一个最基本的要求。这时,可以使用立即执行函数来实现模块化,正如很多JS库比如jQuery以及我们Fundebug都是这样实现的。
1.varmodule=(function(){
2.varN=5
3.
4.functionprint(x){
5.console.log("Theresultis:"+x)
6.}
7.
8.functionadd(a){
9.varx=a+N
10.print(x)
11.}
12.
13.return{
14.description:"Thisisdescription",
15.add:add
16.}
17.})()
18.
19.
20.console.log(module.description)//输出"thisisdescription"
21.
22.module.add(5)//输出“Theresultis:10”
所谓模块化,就是根据需要控制模块内属性与方法的可访问性,即私有或者公开。在代码中,module为一个独立的模块,N为其私有属性,print为其私有方法,decription为其公有属性,add为其共有方法。
6、变量提升
JavaScript会将所有变量和函数声明移动到它的作用域的最前面,这就是所谓的变量提升(Hoisting)。也就是说,无论你在什么地方声明变量和函数,解释器都会将它们移动到作用域的最前面。因此我们可以先使用变量和函数,而后声明它们。但是,仅仅是变量声明被提升了,而变量赋值不会被提升。如果你不明白这一点,有时则会出错:
1.console.log(y)//输出undefined
2.
3.y=2//初始化y
上面的代码等价于下面的代码:
1.vary//声明y
2.
3.console.log(y)//输出undefined
4.
5.y=2//初始化y
为了避免BUG,开发者应该在每个作用域开始时声明变量和函数。
7、柯里化
柯里化,即Currying,可以是函数变得更加灵活。我们可以一次性传入多个参数调用它也可以只传入一部分参数来调用它,让它返回一个函数去处理剩下的参数。
1.varadd=function(x){
2.returnfunction(y){
3.returnx+y
4.}
5.}
6.
7.console.log(add(1)(1))//输出2
8.
9.varadd1=add(1)
10.console.log(add1(1))//输出2
11.
12.varadd10=add(10)
13.console.log(add10(1))//输出11
代码中,我们可以一次性传入2个1作为参数add(1)(1),也可以传入1个参数之后获取add1与add10函数,这样使用起来非常灵活。
8、apply,call与bind方法
JavaScript开发者有必要理解apply、call与bind方法的不同点。它们的共同点是第一个参数都是this,即函数运行时依赖的上下文。
三者之中,call方法是最简单的,它等价于指定this值调用函数:
1.varuser={
2.name:"RahulMhatre",
3.whatIsYourName:function(){
4.console.log(this.name)
5.}
6.}
7.
8.user.whatIsYourName()//输出"RahulMhatre",
9.
10.varuser2={
11.name:"NehaSampat"
12.}
13.
14.user.whatIsYourName.call(user2)//输出"NehaSampat"
·apply方法与call方法类似。两者唯一的不同点在于,apply方法使用数组指定参数,而call方法每个参数单独需要指定:
·apply(thisArg,[argsArray])
1.varuser={
2.greet:"Hello!",
3.greetUser:function(userName){
4.console.log(this.greet+""+userName)
5.}
6.}
7.
8.vargreet1={
9.greet:"Hola"
10.}
11.
12.user.greetUser.call(greet1,"Rahul")//输出"HolaRahul"
13.user.greetUser.apply(greet1,["Rahul"])//输出"HolaRahul"
使用bind方法,可以为函数绑定this值,然后作为一个新的函数返回:
1.varuser={
2.greet:"Hello!",
3.greetUser:function(userName){
4.console.log(this.greet+""+userName)
5.}
6.}
7.
8.vargreetHola=user.greetUser.bind({greet:"Hola"})
9.vargreetBonjour=user.greetUser.bind({greet:"Bonjour"})
10.
11.greetHola("Rahul")//输出"HolaRahul"
12.greetBonjour("Rahul")//输出"BonjourRahul"
9、memoization
Memoization用于优化比较耗时的计算,通过将计算结果缓存到内存中,这样对于同样的输入值,下次只需要中内存中读取结果。
1.functionmemoizeFunction(func)
2.{
3.varcache={}
4.returnfunction()
5.{
6.varkey=arguments[0]
7.if(cache[key])
8.{
9.returncache[key]
10.}
11.else
12.{
13.varval=func.apply(this,arguments)
14.cache[key]=val
15.returnval
16.}
17.}
18.}
19.
20.
21.varfibonacci=memoizeFunction(function(n)
22.{
23.return(n===0||n===1)?n:fibonacci(n-1)+fibonacci(n-2)
24.})
25.
26.console.log(fibonacci(100))//输出354224848179262000000
27.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,直到匹配为止。
1.functionaddMethod(object,name,f)
2.{
3.varold=object[name]
4.object[name]=function()
5.{
6.//f.length为函数定义时的参数个数
7.//arguments.length为函数调用时的参数个数
8.if(f.length===arguments.length)
9.{
10.returnf.apply(this,arguments)
11.}
12.elseif(typeofold==="function")
13.{
14.returnold.apply(this,arguments)
15.}
16.}
17.}
18.
19.
20.//不传参数时,返回所有name
21.functionfind0()
22.{
23.returnthis.names
24.}
25.
26.
27.//传一个参数时,返回firstName匹配的name
28.functionfind1(firstName)
29.{
30.varresult=[]
31.for(vari=0i
32.{
33.if(this.names[i].indexOf(firstName)===0)
34.{
35.result.push(this.names[i])
36.}
37.}
38.returnresult
39.}
40.
41.
42.//传两个参数时,返回firstName和lastName都匹配的name
43.functionfind2(firstName,lastName)
44.{
45.varresult=[]
46.for(vari=0i
47.{
48.if(this.names[i]===(firstName+""+lastName))
49.{
50.result.push(this.names[i])
51.}
52.}
53.returnresult
54.}
55.
56.
57.varpeople={
58.names:["DeanEdwards","AlexRussell","DeanTom"]
59.}
60.
61.
62.addMethod(people,"find",find0)
63.addMethod(people,"find",find1)
64.addMethod(people,"find",find2)
65.
66.
67.console.log(people.find())//输出["DeanEdwards","AlexRussell","DeanTom"]
68.console.log(people.find("Dean"))//输出["DeanEdwards","DeanTom"]
69.console.log(people.find("Dean","Edwards"))//输出["DeanEdwards"]
以上就是小编今天为大家分享的关于Web前端工程师应该知道的JavaScript的10个难点。希望本篇文章能够对正在从事Web前端学习的小伙伴们有所帮助。想要了解更多web前端相关知识记得关注北大青鸟Web培训官网最后祝愿小伙伴们工作顺利!
原文链接:#/a/1190000010371988
业务逻辑很复杂而且多变
『前端的逻辑复杂度基本不如后端』这个只是但从数据处理的角度来看的,前端对于数据处理的确是模板 + 变量一套一展示就好了,这个是挺简单的。
前端逻辑复杂度主要在于数据 + UI + 交互的实现,就比如一个简单的多 tab 页的功能,可以用 CSS 实现、用 JS 实现,JS 可以通过切换 remove DOM 或者添加 classname 隐藏,虽然效果上都可以实现,remove DOM 无法原有结构的状态,添加 classname 的 CSS 方式很难实现初始化状态。除此之外还可能需要对浏览器进行兼容性处理 + 响应式。然后突然来个业务需求说要加个 iframe 嵌入别人的页面,或者改什么效果,如果之前开发的不合理,基本上要重做了。
相比后端,只输出数据模型给前端,如果业务不需要什么字段了,甚至让前端不读取好了,改都不用改。我们几次大的业务平台重构,前端基本要重新开发一遍(效果、交互完全不同),后端模型和数据库则可以递进式的复用、扩展、升级。这也是导致前端需要堆人大力出奇迹的问题。
垂直领域解决方案很难
切页面是没什么难度的,但是在淘宝一到双十一、双十二大促根据经常多变的运营需求切几百个页面就很难了。这已经不是堆人堆外包可以解决的了,所以我们有 TMS 等各种运营系统,前端切模块,运营自己设置图片、文案、组装成运营页面,想改自己在后台改不用麻烦前端。这一套系统是个比较庞大的工程,从模块规范、模块开发工具链、模块发布和版本管理、在线管理、在线可视化搭建、数据填写和数据源导入、页面生成和 CDN 同步等等,都需要前端架构师设计然后开发。设计这个系统是很难的。
再比如富文本内容发布业务需求,光是一个富文本编辑器就很复杂,要实现各种功能和兼容性,更复杂的是要适应业务发展。当时刚开始交接淘宝内容业务的时候,需要重新开发编辑器等,跟后端大神们进行讨论推测未来业务可能会有大量表单而且需要完全的数据驱动,所以我们前端设计开发了 现在有个项目表单很多,用什么技术框架合适? - 知乎 技术产品然后后端有对应的 SDK 进行解析和数据存储、表单生成服务,前端只需要开发组件,然后后端按照业务需求进行配置即可产出内容发布表单。
此外,富文本我们选用了 JSON base 的存储,对比 HTML base 的编辑器,因为淘宝内容详情页充满了各种商品、优惠券、店铺等信息,而且这些信息是需要被理解、识别而且在详情页输出前实时补全最新价格、优惠券可用状态、店铺名等信息的。用传统输出 HTML 的编辑器输出,让后端解析的话复杂度太高了,每一种素材你都需要设计、约束特定的 HTML 标记让后端进行解析。所以我们基于 跳转中... 封装了一套 JSON base 的富文本编辑器,设计了完全数据驱动的插件机制,可以通过配置任意控制要提供的功能等。
虽然知乎的编辑器也是基于 draft-js 开发的,但遇到的业务挑战完全不同。它不需要功能动态变化,因为所有人都一样。然后不知道是后端的数据处理逻辑的问题,它在提交和回填的时候是通过 HTML 作为媒介进行传播,将 draft-js 的 JSON 数据协议转成 HTML 提交给后端存储。所以不同业务场景、特点,需要完全不同的前端解决方案,在开发这些垂直解决方案的时候,业务分析、技术选型、架构设计、开发落地是非常难的。
不同级别的前端面临的难点各不相同,不可一概而论;
业务开发的前端难点在于对业务的理解和把控能力;
平台开发的前端难点在于产品化的把控和推进能力。
观点1:不同级别的前端面临的难点各不相同,不可一概而论。
其他回答有说 CSS 难,有说 CSS 不难的,每个人水平不同,这样争论毫无意义。我刚学前端时觉得 JS/CSS/浏览器兼容问题都很难,现在觉得也就那样,因为前端路子广,办法总比问题多。后来觉得要评估好需求,把控好项目质量比较难,很多时候我们是在干事,在解决问题,不是只埋头写代码,时间一长你会发现前端工作中,技术问题往往比较好解决,反而资源+协作问题比较麻烦。现在对我来说比较难的是快速产品化的能力,如何从无到有去做出一些有价值的东西。
举一个简单粗暴的例子吧:阿里前端很多,P5/P6 一大把,但是 P8/P9 的非常少,为什么?进阶的难点在哪里?
前端开发的难点跟前端进阶的难点是非常相似的。阿里对每个前端层级都有一个标准,这也从侧面回答了这个问题,比如对 P5 来说,难点可能是写好业务代码,保证其灵活性和可维护性,能解决各种适配问题;对 P6 来说则需要独挡一面,能独立 owner 需求,而 P7 则需要在某方面技术有深入理解,等等。
能提出这个问题首先得恭喜题主,说明题主在当前阶段遇到瓶颈了,需要向下一个 level 出击了。
观点2:业务开发的前端难点在于对业务的理解和把控能力。
业务逻辑开发本身并不是难点,谁都可以写。但是对于你自己负责的这块业务,后续业务的发展方向和潜力,你有去了解过吗?当业务方提需求过来时你是只负责执行还是和业务方一起探讨更合理的方案?你有没有给自己负责的产品提过一些建议?做过一些改善措施?如果前端只是作为一个执行者,作为一种被调度的资源,那么即使最终项目取得了好的成绩,跟你有多大关系?你自己会有多大的成就感?
另外一个很重要的点:就是对业务的把控能力。业务方总是会催着上线,开发时间不断被压缩该怎么办?进度不如预期怎么办?开发遇到瓶颈怎么办?发布新功能翻车了怎么办?
我见过有默默加班保证进度的,也有跟需求方重新谈延期的,有发布出问题手足无措的,也有自己默默修复的,有遇到瓶颈一筹莫展的,也有及时跟老板沟通,跟业务方撕逼的… 如何优雅的处理这些问题,有时候比写代码更难。为什么有的人业务代码逻辑混乱,写的一团糟?我不相信是智力问题,反倒更相信是对项目本身没有把控好,本来排了5天工作量的需求被业务方压到了3天,你还能保证写出健壮而不失风度的代码?
观点3:平台开发的前端难点在于产品化的把控和推进能力。
做业务时有人给你提需求,帮你出交互视觉稿,你只要负责写页面就行了。但是在支付宝前端,很多内部平台和技术产品都是技术自己主导,你需要自己发现问题,出方案,设计数据库,自己出页面,这是一个从无到有的创造的过程。并且要保证你做的东西是真正解决问题的,而不是做一些自己觉得很牛逼实际上并没有解决用户痛点的东西,用我老板的话说就是对产品的把控能力,不要跑偏了。前端是最容易做出产品化东西的工程师了,因为后端不会做 UI,UI 不会写代码,唯前端兼顾,这是最大优势。
再一个就是对产品的推进能力了,你做的东西可能需要各种资源?如何争取?可能牵扯到多方利益?如何权衡?东西做出来了如何推广?如何在用户的一片骂声中奋勇前进?
印象中很多平台型产品,刚开始投入使用时都是一片骂声,各种问题,说实话负责这些产品的程序员压力是相当大的,天天被骂还得彻夜帮别人解决问题,还得不断优化系统,你说难不难?
以上三点就是本文所展现的理念,希望能对大家有帮助。