1、变量定义:
let:主要定义一般的变量,但是注意,不可以变量提升,可以防止变量提升。JS会在执行方法前把变量提前声明,声明完成之后才执行方法,但是let会阻止在声明的代码之前操作let的方法。我们可以把它理解为let不提前声明。
防止用户声明了同一个变量,变量覆盖的问题。
function ab(q){q++;console.log(q)}ab(w);let w=1;//报错
function ab(q){q++;console.log(q)}ab(w);var w=1; //2
const:用来定义一些不可修改的常量,一些不可改变的值可以放到变量里面,防止写代码的时候误操作。(身份证、银行卡号等等)。
被const的变量如果进行赋值会直接导致报错。但是如果在他的作用域外操作,将不会产生报错。
注意:ES6有6种变量声明方法,包括ES5的var和function以及ES6的let 、const、import、class。
2.块级作用域:
避免在块级作用域声明函数。编码不当,可能会导致not a function的报错。如果非得用,最好是用变量的方法。例如: var func=function(){};
这样写可以避免报错。if方法不要漏掉大括号,也会避免报错。
3.解构赋值:(两边都必须为数组或者对象,否则报错)
用途:交换变量的值,提取 JSON 数据,设置默认值,遍历 Map 结构
let [a, b, c] = [1, 2, 3];
打印可以得到a=1,b=2,c=3;
如果是下种情况:
let [a, b, c] = [1, 2];
那么c=undefined;也可以在声明里面直接赋值。
在React Hooks的useState方法中就会用到数组的这种形式。
const [aa,setAa]=useState("0");
//打印useState("0")是一个数组,数组第一个值就是你传入的值--“0”,第二个值是一个函数。
字符串也可以被解构赋值,实质就是循环字符串的length遍历赋值 。
当然嵌套对象也是可行的,例如:
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p: [x, { y }] } = obj;
里面的p是一个对象,它对应的就是冒号后边的内容。这里需要注意,p不是变量,第一个y也不是变量,他们都有一个统一的名称:模式。(模式不可以用圆括号,否则会报错!!!!只有变量可以用括号!!!)
所以可以把它理解为:
['Hello', {'World'}]=[x, {y}]
当然也可以将内置对象或者自己声明的对象来解构赋值,比如math、date等,但是声明的变量需要在对象里有对应,否则将找不到对象里的方法或者变量和常量,打印出来将是undefined。
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined
和null
无法转为对象,所以对它们进行解构赋值,都会报错。
4.Set(新数据结构)
Set结构是不会有重复值的。使用任何方法向本数组添加值都会自动删除数组里面相同的内容。
他的好处是删除数组里重复的内容。注意,NaN本来不等于NaN,但是这里会默认NaN只能出现一次。
使用 Set 可以很容易地实现并集、交集和差集。
5.函数的length
length属性的含义是,该函数预期传入的参数个数。如果指定了默认值,将不会算进去。(即使这个默认值是变量也不算)
如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。(暂时没觉得这个属性有多少作用)
6.箭头函数(重点!!!)(所有有return返回值的函数都可以用箭头函数)
var f = v => v;
// 等同于
var f = function (v) {
return v;
};
//如果不需要传参,则用 () 括起来。
var f = () => v;
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = (num1, num2) => {return num1 + num2;}
//在只有一句代码且不是对象的情况下, {}和return可以省去。否则就必须用大括号,因为他是function的函数体。下面有案例讲解
//他等同于
var sum = function(num1, num2) {
return num1 + num2;
};
上面是最常见的使用场景,当然,对象也是很重要的使用场景,使用方法如下:
let obj = id => ({ id: id, name: "Tom" });//只返回对象必须要加一个小括号
你可能很疑惑,为什么不是下面这种写法:
let obj = id => { id: id, name: "Tom" };//报错
let foo = () => { a: 1 };//不报错,但是本方法无意义,也是错误写法
很明显,这种写法是不对的,浏览器会默认把箭头函数的大括号当做函数体,你所认为的对象就不能被解析出来了。
在ES5里,他可以被解释为如下代码:
var obj = function(id){
id: id, name: "Tom"
};
当然,你也可以这样写:
let obj = id => {
return{ id: id, name: "Tom" }
};
箭头函数可以与变量解构结合使用。
const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {
return person.first + ' ' + person.last;
}
传的 { first, last } 其实就是一个未命名的对象。
注意:
(1)他的 this指向不会被改变!!(因为他没有自己的 this,他的 this就是外层代码块的 this,不是箭头函数的名称!!)
即使你多个箭头函数嵌套也是指向最外面的 this。
由于没有this,所以改变this指向的三大方法也失效。(call、apply、bind)
同理,我们在一些内置的监听事件一定要谨慎使用,它的this指向会导致监听事件报错。
(Ps:arguments
、super
、new.target也是指向外部函数对应的变量
)
// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
// ES5
function foo() {
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}
(2)不能 new,只能调用!!!
(3)不要复杂的函数里写箭头函数,尽量使用普通函数。
7.代码优化——尾调用、尾递归
(1)尾调用:
①尾调用必须返回调用的整个函数,且调用之后不能有任何操作。即使是普通的运算也不行。
②尾调用优化的函数必须不能调用外部的变量,否则无法优化。(只保留内层函数的调用帧)
③尾调用优化在严格模式下生效。
(2)尾递归:(递归是自己调用自己)
①尾递归可以避免栈溢出的问题、节约内存。
②尾递归的实现一般都要改写递归函数。
③循环可以使用递归代替,如果要用递归就最好是用尾递归。
④尾递归优化只在严格模式下生效,但是手动优化可以使用循环替代递归。
8.Symbol(第七数据类型)(个人认为没什么用,如果以后有用到再单独写一篇讲。)
es5有6种数据类型,分别为undefined、null、Boolean、String、Number、Object。(object里面包含function、array、data等等)(前五种是基本数据类型)
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false
// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 == s2 // false
s1 === s2 // false
9.set
set是一种数据结构,你可以把它理解为没有重复元素的数组。但是它本身是一种构造函数。
由于他这一特性,我们可以把需要去除重复元素的数组使用set。
var a= [1,2,2,3];
a = new Set(a);//这种写法返回的是set结构
a=Array.from(new Set(a));//这样就能按我们的需求正常返回数组
a=[...new Set(a)];//这也可以返回数组结构,...是扩展运算符。
当然,字符串也是可行的。
var a="hello";
a=[...new Set(a)].join("");
注意,set的NaN==NaN,所以他只会保留一个NaN。
拓展:_proto_在对象里可以看到他可以使用的方法。但是不能使用prototype。prototype只能在函数里面出现。
即对象使用_proto_,函数使用prototype。
所以,Set的构造函数应该使用prototype,他的构造函数就是它本身,即Set.prototype.constructor,包括Date等等内置函数也是如此。如果需要了解他的方法直接去JS控制台打印,这里不详细讲解。
set还有一大作用,就是可以轻松求出交集、并集、差集。
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 半个差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1} 注意:这里变量保存的是a里没有b的元素,如果要全差集,应该像下面一样写。
//全差集
let difference1 = new Set([...a].filter(x => !b.has(x))+[...b].filter(x => !a.has(x)));
// Set {1, 4}
拓展:WeakSet结构
WeakSet和Set类似,区别是成员必须是对象,否则会报错。
10.Map
Map和对象很像,都是键值对,我们可以把它理解为不受限制的对象。但是他的“键”不被限制,除了对象使用的字符串作为键,他还可以用其他的值作为键,包括对象。我们往往使用set、get来设置和取值。
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
他也有很多内置方法,具体这里不讲,自己去浏览器打印,找_proto_。
注意,如果你要用对象作为键,那一定要注意是否是同一对象。
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
你或许很疑惑,为什么是undefined不是555。其实array也是一种对象。如果你把 [ ' a ' ]用一个变量来指向,就不会有问题。
这里就是JS内存地址的知识,地址不同,即使内容相同,也指向不同的堆。
由于这一特性,我们在拿到别人的代码做扩展的时候,就不用担心自己的库与原来库的属性重合的问题。
如果不是对象,是简单的值,那只要他们严格相等,就认为他们是一个键,比如+0===-0。但是NaN是一个特殊的例子,我们记好这个典型就行。在ES6里在使用NaN的方法里基本都会认为NaN是相等的,虽然JS逻辑是完全不相等。
[...myMap]//Map结构转数组
new Map([ [true, 7],[{foo: 3}] ])//数组转Map
Map与对象、json的互转需要写专门的方法。参考如下方法:http://es6.ruanyifeng.com/#docs/set-map
11.class
class部分将单独写一篇博客。移步至:https://blog.csdn.net/YuFun_0923/article/details/89306822
12.module的语法
module部分将单独写一篇博客,移步至:https://blog.csdn.net/YuFun_0923/article/details/89355958
13.编程规范
使用let代替var,常量用const。
同一对象数组,最佳写到一个数组里面,不要分开声明。(解构赋值)
函数传值如果是对象,最好直接在传值里面写,而不是在函数内部定义对象,这样方便后期修改传值顺序(有一部分值可能有默认值传入,就不需要再传)
对象定义:对象静态化,避免修改属性,如果需要修改属性就使用Object.assign方法。
箭头函数替代that绑定this指向。
立即执行函数使用箭头函数。
简单函数采取箭头函数的写法,复杂的还是按原本的写法。
使用class取代需要prototype的对象,使用extends实现继承。
函数使用驼峰式,对象的命名使用首字母大写。
如果是只输入一个值,建议使用export default,如果有多个就不建议这种写法。