Web前端工程师应该知道的JavaScript的10个难点!

JavaScript011

Web前端工程师应该知道的JavaScript的10个难点!,第1张

今天小编要跟大家分享的文章是关于Web前端工程师应该知道的JavaScript的10个难点。相信很多正在学习Web前端知识的小伙伴对于JavaScript存在很多疑问,为了帮助大家更好的学习Web前端知识,成为一名优秀的web前端工程师,今天小编为大家分享了这篇Web前端应该知道的JavaScript难点的问题,下面我们一起看一看吧!

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

今天小编要跟大家分享的文章是关于Web前端工程师应该知道的提高JavaScript

技能的技巧!熟悉web前端工作的小伙伴都知道,JavaScript是前端工程师的必备技能。JavaScript

是一种复杂的语言。如果是你是高级或者初级web开发人员,了解它的基本概念非常重要。本篇文章小编就为大家介绍几种提高JavaScript

技能的技巧,下面让我们一起来看一看吧!

01、变量赋值(值vs引用)

理解JavaScript如何给变量赋值可以帮助我们减少一些不必要的bug。如果你不理解这一点,可能很容易地编写被无意中更改值的代码。

JavaScript总是按照值来给变量赋值。这一部分非常重要:当指定的值是JavaScript的五种基本类型之一(即

Boolean,null,undefined,String和Number)时,将分配实际值。但是,当指定的值是

Array,Function或Object时,将分配对内存中对象的引用给变量。

在以下代码段中,使用var1对var2进行赋值。由于var1是基本类型(String),因此var2的值等于var1的String

值,并且可以认为此时与var1完全不同。因此,重新赋值var2对var1没有影响。

letvar1='Mystring'

letvar2=var1

var2='Mynewstring'

console.log(var1)

//'Mystring'

console.log(var2)

//'Mynewstring'

接着,与对象赋值进行比较。

letvar1={name:'Jim'}

letvar2=var1

var2.name='John'

console.log(var1)

//{name:'John'}

console.log(var2)

//{name:'John'}

如果你期望它会像原始类型赋值那样,很可能会出问题!如果你创建了一个无意中会改变对象的函数,就会出现一些非预期的行为。

02、闭包

闭包是一个重要的JavaScript模式,可以私有访问变量。在本例中,createGreeter返回一个匿名函数,这个函数可以访问参数

greeting(在这里是“Hello”)。在后续的调用中,sayHello将有权访问这个greeting!

functioncreateGreeter(greeting){

returnfunction(name){

console.log(greeting+','+name)

}

}

constsayHello=createGreeter('Hello')

sayHello('Joe')

//Hello,Joe

在更真实的场景中,你可以设想一个初始函数apiConnect(apiKey),它返回一些使用APIkey的方法。在这种情况下,apiKey

只需要提供一次即可。

functionapiConnect(apiKey){

functionget(route){

returnfetch(`${route}?key=${apiKey}`)

}

functionpost(route,params){

returnfetch(route,{

method:'POST',

body:JSON.stringify(params),

headers:{

'Authorization':`Bearer${apiKey}`

}

})

}

return{get,post}

}

constapi=apiConnect('my-secret-key')

//NoneedtoincludetheapiKeyanymore

api.get('#/get-endpoint')

api.post('#/post-endpoint',{name:'Joe'})

03、解构

JavaScript参数解构可以从对象中干中提取所需属性的常用方法。

constobj={

ame:'Joe',

food:'cake'

}

const{name,food}=obj

console.log(name,food)

//'Joe''cake'

如果要以其他名称提取属性,可以使用如下方式:

constobj={

ame:'Joe',

food:'cake'

}

const{name:myName,food:myFood}=obj

console.log(myName,myFood)

//'Joe''cake'

解构经常也用于直接用于提取传给函数的参数。如果你熟悉React,可能已经见过这个:

constperson={

ame:'Eddie',

age:24

}

functionintroduce({name,age}){

console.log(`I'm${name}andI'm${age}yearsold!`)

}

console.log(introduce(person))

//"I'mEddieandI'm24yearsold!"

04、展开运算

ES6的一个常用之一的特性就是展开(...)运算符了,在下面的例子中,Math.max不能应用于arr

数组,因为它不将数组作为参数,但它可以将各个元素作为参数传入。展开运算符...可用于提取数组的各个元素。

constarr=[4,6,-1,3,10,4]

constmax=Math.max(...arr)

console.log(max)

//10

05、剩余参数

剩余参数语法和展开语法看起来的一样的,不同的是展开语法是为了结构数组和对象而剩余参数和展开运算符是相反的,剩余参数收集多个元素合成一个数组。

functionmyFunc(...args){

console.log(args[0]+args[1])

}

myFunc(1,2,3,4)

//3

restparameters和arguments的区别

1.arguments是伪数组,包含所有的实参

2.剩余参数是标准的数组,可以使用数组的方法

06、数组方法

JavaScript数组方法通常可以提供令人难以置信的、优雅的方法来执行所需的数据转换。作为StackOverflow

的贡献者,我经常看到关于如何以某种方式操纵对象数组的问题,这往往也是数组方法的完美用例。

map、filter、reduce

JavaScript数组方法map、filter和reduce容易混淆,这些都是转换数组或返回聚合值的有用方法。

map:返回一个数组,其中每个元素都使用指定函数进行过转换。

constarr=[1,2,3,4,5,6]

constmapped=arr.map(el=>el+20)

console.log(mapped)

//[21,22,23,24,25,26]

filter:返回一个数组,只有当指定函数返回true时,相应的元素才会被包含在这个数组中。

constarr=[1,2,3,4,5,6]

constfiltered=arr.filter(el=>el===2||el===4)

console.log(filtered)

//[2,4]

reduce:按函数中指定的值累加

constarr=[1,2,3,4,5,6]

constreduced=arr.reduce((total,current)=>total+current)

console.log(reduced)

//21

find,findIndex,indexOf

find:返回与指定条件匹配的第一个实例,如果查到不会继续查找其他匹配的实例。

constarr=[1,2,3,4,5,6,7,8,9,10]

constfound=arr.find(el=>el>5)

console.log(found)

//6

再次注意,虽然5之后的所有元素都满足条件,但是只返回第一个匹配的元素。当你发现匹配项时,通常会中断for循环,在这种情况下,这实际上非常有用。

findIndex:这与find几乎完全相同,但不是返回第一个匹配元素,而是返回第一个匹配元素的索引。

constarr=['Nick','Frank','Joe','Frank']

constfoundIndex=arr.findIndex(el=>el==='Frank')

console.log(foundIndex)

//1

indexOf:与findIndex几乎完全相同,但它不是将函数作为参数,而是采用一个简单的值。

当w你需要更简单的逻辑并且不需要使用函数来检查是否存在匹配时,可以使用此方法。

constarr=['Nick','Frank','Joe','Frank']

constfoundIndex=arr.indexOf('Frank')

console.log(foundIndex)

//1

push,pop,shift,unshift

push:这是一个相对简单的方法,它将一个项添加到数组的末尾。它就地修改数组,函数本身会返回添加到数组中的项。

letarr=[1,2,3,4]

constpushed=arr.push(5)

console.log(arr)

//[1,2,3,4,5]

console.log(pushed)

//5

pop:这将从数组中删除最后一项。同样,它在适当的位置修改数组,函数本身返回从数组中删除的项。

letarr=[1,2,3,4]

constpopped=arr.pop()

console.log(arr)

//[1,2,3]

console.log(popped)

//4

shift:从数组中删除第一项。同样,它在适当的位置修改数组。函数本身返回从数组中删除的项。

letarr=[1,2,3,4]

constshifted=arr.shift()

console.log(arr)

//[2,3,4]

console.log(shifted)

//1

unshift:将一个或多个元素添加到数组的开头。同样,它在适当的位置修改数组。与许多其他方法不同,函数本身返回数组的新长度。

letarr=[1,2,3,4]

constunshifted=arr.unshift(5,6,7)

console.log(arr)

//[5,6,7,1,2,3,4]

console.log(unshifted)

//7

splice,slice

splice:通过删除或替换现有元素和/或添加新元素来更改数组的内容,此方法会修改了数组本身。

下面的代码示例的意思是:在数组的位置1上删除0个元素,并插入b。

letarr=['a','c','d','e']

arr.splice(1,0,'b')

slice:从指定的起始位置和指定的结束位置之前返回数组的浅拷贝。如果未指定结束位置,则返回数组的其余部分。

重要的是,此方法不会修改数组,而是返回所需的子集。

letarr=['a','b','c','d','e']

constsliced=arr.slice(2,4)

console.log(sliced)

//['c','d']

console.log(arr)

//['a','b','c','d','e']

sort

sort:根据提供的函数对数组进行排序。这个方法就地修改数组。如果函数返回负数或0,则顺序保持不变。如果返回正数,则交换元素顺序。

letarr=[1,7,3,-1,5,7,2]

constsorter=(firstEl,secondEl)=>firstEl-secondEl

arr.sort(sorter)

console.log(arr)

//[-1,1,2,3,5,7,7]

07、Generators(生成器)

生成器是一种特殊的行为,实际上是一种设计模式,我们通过调用next()方法来遍历一组有序的值。想象一下,例如使用遍历器对数组[1,2,3,4,5]进行遍历。第一次调用next()方法返回1,第二次调用next()方法返回2,以此类推。当数组中的所有值都返回后,调用next()方法将返回null或false或其它可能的值用来表示数组中的所有元素都已遍历完毕。

function*greeter(){

yield'Hi'

yield'Howareyou?'

yield'Bye'

}

constgreet=greeter()

console.log(greet.next().value)

//'Hi'

console.log(greet.next().value)

//'Howareyou?'

console.log(greet.next().value)

//'Bye'

console.log(greet.next().value)

//undefined

使用生成器生成无限个值:

function*idCreator(){

leti=0

while(true)

yieldi++

}

constids=idCreator()

console.log(ids.next().value)

//0

console.log(ids.next().value)

//1

console.log(ids.next().value)

//2

//etc...

08、恒等运算符(===)与相等运算符(==)

大家一定要知道JavaScript中的恒等运算符(===)和相等运算符(==)之间的区别!

==运算符在比较值之前会进行类型转换,而===运算符在比较之前不会进行任何类型转换。

console.log(0=='0')

//true

console.log(0==='0')

//false

09、对象比较

我看到JavaScript新手所犯的错误是直接比较对象。变量指向内存中对象的引用,而不是对象本身!实际比较它们的一种方法是将对象转换为JSON

字符串。这有一个缺点:对象属性顺序不能保证!比较对象的一种更安全的方法是引入专门进行深度对象比较的库(例如,lodash的isEqual)。

下面的对象看起来是相等的,但实际上它们指向不同的引用。

constjoe1={name:'Joe'}

constjoe2={name:'Joe'}

console.log(joe1===joe2)

//false

相反,下面的计算结果为true,因为一个对象被设置为与另一个对象相等,因此指向相同的引用(内存中只有一个对象)。

constjoe1={name:'Joe'}

constjoe2=joe1

console.log(joe1===joe2)

//true

相反,以下计算结果为true,因为一个对象设置为等于另一个对象,因此指向相同的引用(内存中只有一个对象)。

constjoe1={name:'Joe'}

constjoe2=joe1

console.log(joe1===joe2)

//true

10、回调函数

很多人都被JavaScript回调函数吓倒了!他们很简单,举个例子。console.log函数作为回调传递给myFunc。

它在setTimeout完成时执行。

functionmyFunc(text,callback){

setTimeout(function(){

callback(text)

},2000)

}

myFunc('Helloworld!',console.log)

//'Helloworld!'

11、Promises

一旦你理解了JavaScript回调,很快就会发现自己陷入了“回调地狱”中。这个时候可以使用promise,将异步逻辑包装在promise

中,成功时resolve或在失败时reject使用“then”来处理成功的情况,使用catch来处理异常。

constmyPromise=newPromise(function(res,rej){

setTimeout(function(){

if(Math.random()

returnres('Hooray!')

}

returnrej('Ohno!')

},1000)

})

myPromise

.then(function(data){

console.log('Success:'+data)

})

.catch(function(err){

console.log('Error:'+err)

})

//IfMath.random()returnslessthan0.9thefollowingislogged:

//"Success:Hooray!"

//IfMath.random()returns0.9orgreaterthefollowingislogged:

//"Error:Onno!"

12、Async/Await

在掌握了promise的用法后,你可能也会喜欢asyncawait,它只是一种基于promise

的“语法糖”。在下面的示例中,我们创建了一个async函数,并awaitgreeterpromise。

constgreeter=newPromise((res,rej)=>{

setTimeout(()=>res('Helloworld!'),2000)

})

asyncfuncti