这篇文章主要让你学会工作当中常用的es6技巧,以及扩展如实现数据双向绑定,class用es5如何实现、如何给伪数组添加迭代器等等。
前言:
以下文章纯属个人理解,便于记录学习,肯定有理解错误或理解不到位的地方,意在站在前辈的肩膀,分享个人对技术的通俗理解,共同成长!
后续我会陆陆续续更新javascript方面,尽量把javascript这个学习路径体系都写一下 包括前端所常用的es6、angular、react、vue、nodejs、koa、express、公众号等等 都会从浅到深,从入门开始逐步写,希望能让大家有所收获,也希望大家关注我~
这篇文章主要让你学会工作当中常用的es6技巧,以及扩展如实现数据双向绑定,class用es5如何实现、如何给伪数组添加迭代器等等。
正文:
var、let、const
// 1.var存在变量作用域的提升 console.log(a) // 打印输出 undefined var a = 1 // 怎么理解作用域的提升呢? // var str = 'hello swr' // function(){ // console.log(str) // 打印输出 undefined // var str = 'goodbye swr' // } // test() // 上面这段代码实际上是 var str = 'hello swr' function(){ var str console.log(str) // 打印输出undefined // 实际上就是var声明的变量,拿到 // 当前作用域的最顶层,而此时尚未赋值 // 只是声明,所以打印出undefined,而非当运行 // 到这段代码时才声明,优先声明, // 当运行到那行的时候,实际上是赋值 // 同样的,function xx(){}也存在作用域提升 str = 'goodbye swr' } test() // var 不存在块级作用域的概念 // 我的理解是在es6之前,是没有块级作用域的概念, // 变量只有遇到函数的时候才会变为局部变量 { var str 1 = 'hello swr' } console.log(str1) // 打印输出 hello swr // 2.let // 2.1 不存在变量作用域提升,这样可以避免了我们还没声明变量就拿变量来用 // 2.2 同一作用域的同一个变量不能够重复声明,避免我们重复声明变量 // 2.3 let声明的变量不会到全局上 // 2.4 let和代码块{}结合使用会形成块级作用域 // 2.1 // console.log(a) // 报错,a未声明 // let a = 'hello swr' // 2.2 // let a = 'hello swr' // let a = 'hello swr' // 报错,变量被重复声明 // 2.3 // let a = 'hello swr' // console.log(window.a) // undefined // 2.4 // 在代码块以外调用str2,会报错 { let str2 = 'hello swr' } console.log(str2) // 报错,未找到变量 // 上面这种写法,也有点类型es6之前的立即执行函数 (function(){ var str2 = 'hello swr' })() // 一个例子 // 使用var,会发现最终console.log中打印的i都是3 // 因为for循环不是函数,而此时var i是处于全局当中 // for循环是同步代码,所以会执行完同步代码后 // 再执行setTimeout的异步代码,此时i已为3,所以打印出来都是3 for(var i = 0;i < 3;i++){ setTimeout(function(){ console.log(i) },1000) } // 那么我们用let试下 // let和代码块结合起来使用会形成块级作用域 // 那么当for时,这3个setTimeout会分别在3个不同的块级作用域 // 当执行setTimeout的console.log(i)时,会先寻找最近的块级作用域中的i // 所以会依次打印出0 1 2 for(let j = 0;j < 3;j++){ setTimeout(function(){ console.log(i) },1000) } // 3.const // 3.1 const和let基本上可以说是完全一致的, //但是const声明的对象不能更改其指向的引用地址(即堆区) // 3.1 // 当用普通值赋值给const声明的变量后,再重新赋值时 // 值引用会被更改,所以会报错 const STR1 = 'hello swr' STR1 = 'goodbye swr' // 报错,Assignment to constant variable // 当我们修改这个引用地址里面的内容时,则不会报错 // 因为这个变量是指向这个引用地址的 const OBJ = {name:"swr"} OBJ.name = 'hello swr' console.log(OBJ) // {name:"hello swr"} // 但是当我们把这个变量重新赋值一个引用地址时,则会报错 OBJ = {} // 报错
解构赋值
解构赋值主要分为对象的解构和数组的解构,在没有解构赋值的时候,我们赋值是这样的
let arr = [0,1,2] let a = arr[0] let b = arr[1] let c = arr[2]
这样写很繁琐,那么我们有没办法既声明,又赋值,更优雅的写法呢?肯定是有的,那就是解构赋值,解构赋值,简单理解就是等号的左边和右边相等。
数组的解构赋值
let arr = [0,1,2] let [a,b,c] = arr console.log(a) // 0 console.log(b) // 1 console.log(c) // 2
但是很多时候,数据并非一一对应的,并且我们希望得到一个默认值
let arr = [,1,2] let [a='我是默认值',b,c] = arr console.log(a) // '我是默认值' console.log(b) // 1 console.log(c) // 2 // 从这个例子可以看出,在解构赋值的过程中,a=undefined时,会使用默认值 // 那么当a=null时呢?当a=null时,那么a就不会使用默认值,而是使用null // 数组的拼接 let a = [0,1,2] let b = [3,4,5] let c = a.concat(b) console.log(c) // [0,1,2,3,4,5] let d = [...a,...b] console.log(d) // [0,1,2,3,4,5] // 数组的克隆 // 假如我们简单地把一个数组赋值给另外一个变量 let a = [0,1,2,3] let b = a b.push(4) console.log(a) // [0,1,2,3,4] console.log(b) // [0,1,2,3,4] // 因为这只是简单的把引用地址赋值给b,而不是重新开辟一个内存地址,所以 // a和b共享了同一个内存地址,该内存地址的更改,会影响到所有引用该地址的变量 // 那么用下面的方法,把数组进行克隆一份,互不影响 let a = [0,1,2,3] let b = [...a] b.push(4) console.log(a) // [0,1,2,3] console.log(b) // [0,1,2,3,4]
对象的解构赋值
对象的解构赋值和数组的解构赋值其实类似,但是数组的数组成员是有序的 而对象的属性则是无序的,所以对象的解构赋值简单理解是等号的左边和右边的结构相同
let {name,age} = {name:"swr",age:28} console.log(name) // 'swr' console.log(age) // 28
对象的解构赋值是根据key值进行匹配
// 这里可以看出,左侧的name和右侧的name,是互相匹配的key值 // 而左侧的name匹配完成后,再赋值给真正需要赋值的Name let { name:Name,age } = { name:'swr',age:28 } console.log(Name) // 'swr' console.log(age) // 28
那么当变量已经被声明了呢?
let name,age // 需要用圆括号,包裹起来 ({name,age} = {name:"swr",age:28}) console.log(name) // 'swr' console.log(age) // 28
变量能否也设置默认值?
let {name="swr",age} = {age:28} console.log(name) // 'swr' console.log(age) // 28 // 这里规则和数组的解构赋值一样,当name = undefined时,则会使用默认值 let [a] = [{name:"swr",age:28}] console.log(a) // {name:"swr",age:28} let { length } = "hello swr" console.log(length) // 9 function ajax({method,url,type='params'}){ console.log(method) // 'get' console.log(url) // '/' console.log(type) // 'params' } ajax({method:"get",url:"/"})
扩展运算符
我们先看下代码
// 在以往,我们给函数传不确定参数数量时,是通过arguments来获取的 function sum() { console.log(arguments) // { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6 } // 我们可以看出,arguments不是一个数组,而是一个伪数组 let total = 0 let { length } = arguments for(let i = 0;i < length;i++){ total += arguments[i] } return total } console.log(sum(1,2,3,4,5,6)) // 21 // 接下来我们用扩展运算符看看 function sum(...args){ // 使用...扩展运算符 console.log(args) // [ 1, 2, 3, 4, 5, 6 ] args是一个数组 return eval(args.join('+')) } console.log(sum(1,2,3,4,5,6)) // 21
得到的args是一个数组,直接对数组进行操作会比对伪数组进行操作更加方便,还有一些注意点需要注意
// 正确的写法 扩展运算符只能放在最后一个参数 function sum(a,b,...args){ console.log(a) // 1 console.log(b) // 2 console.log(args) // [ 3, 4, 5, 6 ] } sum(1,2,3,4,5,6) // 错误的写法 扩展运算符只能放在最后一个参数 function sum(...args,a,b){ // 报错 } sum(1,2,3,4,5,6)
我们可以对比下扩展运算符的方便之处
// 以往我们是这样拼接数组的 let arr1 = [1,2,3] let arr2 = [4,5,6] let arr3 = arr1.concat(arr2) console.log(arr3) // [ 1, 2, 3, 4, 5, 6 ] // 现在我们用扩展运算符看看 let arr1 = [1,2,3] let arr2 = [4,5,6] let arr3 = [...arr1,...arr2] console.log(arr3) // [ 1, 2, 3, 4, 5, 6 ] // 以往我们这样来取数组中最大的值 function max(...args){ return Math.max.apply(null,args) } console.log(max(1,2,3,4,5,6)) // 6 // 现在我们用扩展运算符看看 function max(...args){ return Math.max(...args) // 把args [1,2,3,4,5,6]展开为1,2,3,4,5,6 } console.log(max(1,2,3,4,5,6)) // 6 // 扩展运算符可以把argument转为数组 function max(){ console.log(arguments) // { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6 } let arr = [...arguments] console.log(arr) // [1,2,3,4,5,6] } max(1,2,3,4,5,6) // 但是扩展运算符不能把伪数组转为数组(除了有迭代器iterator的伪数组,如arguments) let likeArr = { "0":1,"1":2,"length":2 } let arr = [...likeArr] // 报错 TypeError: likeArr is not iterable // 但是可以用Array.from把伪数组转为数组 let likeArr = { "0":1,"1":2,"length":2 } let arr = Array.from(likeArr) console.log(arr) // [1,2]
对象也可以使用扩展运算符
// 以往我们这样合并对象 let name = { name:"邵威儒" } let age = { age:28 } let person = {} Object.assign(person,name,age) console.log(person) // { name: '邵威儒', age: 28 } // 使用扩展运算符 let name = { name:"邵威儒" } let age = { age:28 } let person = {...name,...age} console.log(person) // { name: '邵威儒', age: 28 }
需要注意的是,通过扩展运算符和Object.assign对对象进行合并的行为,是属于浅拷贝,那么我们在开发当中,经常需要对对象进行深拷贝,接下来我们看看如何进行深拷贝。
// 方法一:利用JSON.stringify和JSON.parse let swr = { name:"邵威儒", age:28 } let swrcopy = JSON.parse(JSON.stringify(swr)) console.log(swrcopy) // { name:"邵威儒",age:28 } // 此时我们修改swr的属性 swr.age = 29 console.log(swr) // { name:"邵威儒",age:29 } // 但是swrcopy却不会受swr影响 console.log(swrcopy) // { name:"邵威儒",age:28 } // 这种方式进行深拷贝,只针对json数据这样的键值对有效 // 对于函数等等反而无效,不好用,接着继续看方法二、三。 // 方法二: function deepCopy(fromObj,toObj) { // 深拷贝函数 // 容错 if(fromObj === null) return null // 当fromObj为null if(fromObj instanceof RegExp) return new RegExp(fromObj) // 当fromObj为正则 if(fromObj instanceof Date) return new Date(fromObj) // 当fromObj为Date toObj = toObj || {} for(let key in fromObj){ // 遍历 if(typeof fromObj[key] !== 'object'){ // 是否为对象 toObj[key] = fromObj[key] // 如果为普通值,则直接赋值 }else{ toObj[key] = new fromObj[key].constructor // 如果为object,则new这个object指向的构造函数 deepCopy(fromObj[key],toObj[key]) // 递归 } } return toObj } let dog = { name:"小白", sex:"公", firends:[ { name:"小黄", sex:"母" } ] } let dogcopy = deepCopy(dog) // 此时我们把dog的属性进行修改 dog.firends[0].sex = '公' console.log(dog) // { name: '小白', // sex: '公', // firends: [ { name: '小黄', sex: '公' }] } // 当我们打印dogcopy,会发现dogcopy不会受dog的影响 console.log(dogcopy) // { name: '小白', // sex: '公', // firends: [ { name: '小黄', sex: '母' } ] }
// 方法三: let dog = { name:"小白", sex:"公", firends:[ { name:"小黄", sex:"母" } ] } function deepCopy(obj) { if(obj === null) return null if(typeof obj !== 'object') return obj if(obj instanceof RegExp) return new RegExp(obj) if(obj instanceof Date) return new Date(obj) let newObj = new obj.constructor for(let key in obj){ newObj[key] = deepCopy(obj[key]) } return newObj } let dogcopy = deepCopy(dog) dog.firends[0].sex = '公' console.log(dogcopy)