使用 () 还是 {},要看你定义的函数的参数是什么样的,具体看下面的例子。
第一种:当函数的参数是一个对象时,需要使用 {}。这样在函数里面能通过参数的相同的 key 获取到参数对象里的 key对应的值。
function demo({name, age}) { console.log(`姓名:${name}`) console.log(`年龄:${age}`)
}
demo({ name: '张三', age: 24})
第二种:当函数里的参数是多个时,这个时候用 ()。其实,这个不是结构赋值,真的只是 ES5 里的形参而已,只不过你的代码里用到了 ES6 中的参数默认值设置而已。
function demo(name = "张三", age = 30) { console.log("姓名:" + name) console.log("nianl:" + age)
}
demo()
今天小编要跟大家分享的文章是关于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
模块化在项目中十分的重要,一个复杂的项目肯定有很多相似的功能模块,如果每次都需要重新编写模块肯定既费时又耗力。但是引用别人编写模块的前提是要有统一的“打开姿势”,如果每个人有各自的写法,那么肯定会乱套,下面介绍几种JS的模块化的规范。一:模块化进程一:script标签
这是最原始的 JavaScript 文件加载方式,如果把每一个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,也就是定义在 window 对象中,不同模块的接口调用都是一个作用域中,一些复杂的框架,会使用命名空间的概念来组织这些模块的接口。
缺点:
1、污染全局作用域
2、开发人员必须主观解决模块和代码库的依赖关系
3、文件只能按照script标签的书写顺序进行加载
4、在大型项目中各种资源难以管理,长期积累的问题导致代码库混乱不堪
二:模块化进程二:CommonJS规范
该规范的核心思想是允许模块通过require方法来同步加载所要依赖的其他模块,然后通过 exports 或 module.exports 来导出需要暴露的接口。
require("module")
require("../file.js")
exports.doStuff = function(){}
module.exports = someValue
优点:
1、简单并容易使用
2、服务器端模块便于重用
缺点:
1、同步的模块加载方式不适合在浏览器环境中,同步意味着阻塞加载,浏览器资源是异步加载的
2、不能非阻塞的并行加载多个模块
module.exports与exports的区别
1、exports 是指向的 module.exports 的引用
2、module.exports 初始值为一个空对象 {},所以 exports 初始值也是 {}
3、require() 返回的是 module.exports 而不是 exports
exports示例:
// app.js
var circle = require('./circle')
console.log(circle.area(4))
// circle.js
exports.area = function(r){
return r * r * Math.PI
}
module.exports示例:
// app.js
var area = require('./area')
console.log(area(4))
// area.js
module.exports = function(r){
return r * r * Math.PI
}
错误的情况:
// app.js
var area = require('./area')
console.log(area(4))
// area.js
exports = function(r){
return r * r * Math.PI
}
其实是对 exports 进行了覆盖,也就是说 exports 指向了一块新的内存(内容为一个计算圆面积的函数),也就是说 exports 和 module.exports 不再指向同一块内存,也就是说此时 exports 和 module.exports 毫无联系,也就是说 module.exports 指向的那块内存并没有做任何改变,仍然为一个空对象{},也就是说area.js导出了一个空对象,所以我们在 app.js 中调用 area(4) 会报 TypeError: object is not a function 的错误。
总结:当我们想让模块导出的是一个对象时, exports 和 module.exports 均可使用(但 exports 也不能重新覆盖为一个新的对象),而当我们想导出非对象接口时,就必须也只能覆盖 module.exports 。
三:模块化进程三:AMD规范
由于浏览器端的模块不能采用同步的方式加载,会影响后续模块的加载执行,因此AMD(Asynchronous Module Definition异步模块定义)规范诞生了。
AMD标准中定义了以下两个API
1、require([module], callback)
2、define(id, [depends], callback)
require接口用来加载一系列模块,define接口用来定义并暴露一个模块。
示例:
define("module", ["dep1", "dep2"], function(d1, d2){
return someExportedValue
})
require(["module", "../file"], function(module, file){ /* ... */ })
优点:
1、适合在浏览器环境中异步加载模块
2、可以并行加载多个模块
缺点:
1、提高了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不顺畅
2、不符合通用的模块化思维方式,是一种妥协的实现
四:模块化进程四:CMD规范
CMD(Common Module Definition)规范和AMD很相似,尽量保持简单,并与CommonJS和Node.js的 Modules 规范保持了很大的兼容性。在CMD规范中,一个模块就是一个文件。
示例:
define(function(require, exports, module){
var $ = require('jquery')
var Spinning = require('./spinning')
exports.doSomething = ...
module.exports = ...
})
优点:
1、依赖就近,延迟执行
2、可以很容易在 Node.js 中运行
缺点:
1、依赖 SPM 打包,模块的加载逻辑偏重
AMD和CMD的区别
AMD和CMD起来很相似,但是还是有一些细微的差别,让我们来看一下他们的区别在哪里:
1、对于依赖的模块,AMD是提前执行,CMD是延迟执行。
2、AMD推崇依赖前置;CMD推崇依赖就近,只有在用到某个模块的时候再去require。看代码:
// AMD
define(['./a', './b'], function(a, b){ // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
})
// CMD
define(function(require, exports, module){
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b')
// 依赖可以就近书写
b.doSomething()
// ...
})
3、AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。
五:模块化进程五:ES6模块化
EcmaScript6标准增加了JavaScript语言层面的模块体系定义。ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西。
在 ES6 中,我们使用export关键字来导出模块,使用import关键字引用模块。需要说明的是,ES6的这套标准和目前的标准没有直接关系,目前也很少有JS引擎能直接支持。因此Babel的做法实际上是将不被支持的import翻译成目前已被支持的require。
尽管目前使用import和require的区别不大(本质上是一回事),但依然强烈推荐使用import关键字,因为一旦JS引擎能够解析ES6的import关键字,整个实现方式就会和目前发生比较大的变化。如果目前就开始使用import关键字,将来代码的改动会非常小。
示例:
import "jquery"
export functiondoStuff(){}
module "localModule" {}
优点:
1、容易进行静态分析
2、面向未来的 EcmaScript 标准
缺点:
1、原生浏览器端还没有实现该标准
2、全新的命令字,新版的 Node.js才支持