简介
JS 组成部分
由三大部分组成
- ES (JS语法规范)
- DOM (操作网页元素API)
- BOM (操作浏览器API)
ES6 是什么?
在2015年之前,ES版本都是以‘ES+数字’方式进行命名,最高发布到ES5.1;但2015年之后,ES版本命名发生改变,变为‘ES+年份’进行命名,并且逐年更新,不仅是命名和更新频率发生改变,而且ES2015推出了很多新特性和对JS的缺陷提出很多补救的语法,让JS更健壮,功能更强大,所以2015年对于JS来说是一个里程碑的年份,行业里对2015年及其之后推出的所有ES版本统称为ES6,因此ES6是个泛指,其包含ES2015、ES2017等
新语法
let const声明
var 缺陷
1、缺少块级作用域
2、可在同一作用域下重复声明同一变量
3、无常量概念
4、在for循环里存在污染变量问题
5、变量提升问题(先使用后声明)
6、在全局下,定义的变量其实是window对象的属性(污染window对象)
let/const 的出现就是为了解决 var 的缺陷
var btns = document.getElementsByTagName("button"); // 4个btn
for(var i=0; i<btns.length; i++){
btns[i].onclick = function(){
console.log(i);
}
}
// 永远输出 4
for(let i=0; i<btns.length; i++){
btns[i].onclick = function(){
console.log(i);
}
}
// 输出对应索引
上面例子原因:var声明的变量是全局,而let声明的变量是局部的
const 定义常量 必须注意以下:
1、一旦声明就必须赋值
2、一旦赋值就不能修改
3、具有块作用域
4、声明引用类型常量,引用类型里的值可以修改
在全局中,用
var声明的变量其实是window对象的属性(函数声明也一样),而用let/const声明的变量不是window对象的属性
模板字符串
一种定义字符串的新方式,让字符串拼接更加简单,并且支持换行
// 以前的方式
var oldOneStr = "xxx";
var oldTwoStr = 'bbb';
// 新方式
let newStr1 = `bsss${name}hhh`;
let newStr2 = `测试
你是谁`;
解构赋值
从
数组或对象中提取值,并对变量赋值,这一系列操作称之为解构,主要用于简化代码
没有解构前
var obj = {
name: "wt",
age: 18,
desc:“谢谢彩信相册”
}
var name = obj.name,
age = obj.age,
desc = obj.desc;
出现解构后
var obj = {
name: "wt",
age: 18,
desc:“谢谢彩信相册”
}
// 解构时,左边的变量名必须与右边对象中属性名完全一样
var {name, desc, age} = obj;
解构失败的变量,值将为
undefined
完全解构
// 对象
var obj = {
name: "wt",
age: 18,
desc:“谢谢彩信相册”
};
var {name, desc, age} = obj;
// 数组
var arr = [20, 30, 50];
var [a, b, c] = arr;
部分解构
// 对象
var obj = {
name: "wt",
age: 18,
desc:“谢谢彩信相册”
};
var {desc} = obj;
// 数组
var arr = [20, 30, 50];
var [,, c] = arr;
对象解构并重命名
var {name: titleName} = obj; // 针对对象
多层嵌套如何解构
// 对象
var obj = {
name: 'wt',
age: 28,
ex: {
isMerry: true
}
}
var {name, age, ex: { isMerry }} = obj;
// 数组
var arr = [1, 2, [100, 200, 300]];
var [x,,[,y]] = arr
其他应用 (交换变量值)
var str1 = '234',
str2 = '567';
[str1, str2] = [str2, str1];
对象的简化写法
以前
var title = "a", desc = "xxx";
function go(){}
var obj = {
title:title,
desc: desc,
go: go,
init: function(){}
};
简化后
var title = "a", desc = "xxx";
function go(){}
var obj = {
title,
desc,
go,
init(){}
};
当对象的属性名和变量名相同时,可以使用简写
函数参数
默认值
以前做法
function test(a, b, c){
a = a || 0;
b = b || 0;
c = c || 0;
return a+b+c;
}
现在做法
function test(a=0, b=0, c=0){
return a+b+c;
}
解构
函数参数:数组
// 数组解构必须遵循顺序
function test([a, b, c]){
return a+b+c;
}
test([10, 20, 30]);
函数参数:对象 (推荐)
// 比较推荐此方式作为函数参数进行解构,因为无需按顺序
function test({c, b, d}){
console.log(b);
}
test({
d: 12,
c: '232',
b: true
})
解构默认值
如果函数参数为对象并解构赋值,但函数调用时,并没有传实参,此时将会语法报错
function test({c, b, d}){
console.log(b);
}
test() // 相当于传了一个null过去进行解构,如:{c, b, d}=null 语法报错
test({}) // 预期要传一个对象过去,至少需要传空对象{} 语法正确
从上面代码可以知道解构对象参数时,至少需要一个空对象{}传过去,但为了不传实参也不会出现语法错误,我们需要使用解构对象默认值,如下
function test({a,b}={}){
console.log(a,b)
}
test() // undefined,undefined 语法正确
上面做法虽然语法正确了,但比非我最终想得到的结果,我希望在不穿任何值的情况下,解构出来的a和b有默认值,所以还要设置解构对象属性默认值,如下
function test({a=0,b=0}={}){
console.log(a,b)
}
test() // 0,0
上面写法已经是最终版的写法,但综合前面的知识,还想解构重命名(这个是伪需求),如下
function test({a:one=1,b:two=2}={}){
console.log(one,two);
}
test();
未知剩余参数 Rest
ES6之前
function test(){
console.log(arguments); // 函数内部默认变量,类数组
}
test();
ES6之后 (Rest参数取代了arguments)
function test(a, b, ...c){
console.log(c); // [3,4,5]
}
test(1,2,3,4,5)
注意事项:
1、rest参数只能用于函数定义的形参中,以...变量名形式出现
2、rest参数只能出现在函数形参最后一位
3、rest参数实际上用数组把未知剩余参数装起来,数据类型就是数组
与解构相结合
let [a, ...rest] = []; // a=undefined, rest=[]
let [a, ...rest] = [1,2,3]; // a=1, rest=[2,3]
// 数组
function test([c, d, ...z]){
console.log(z); // [3,4,5]
}
test([1,2,3,4,5])
// 对象
function testObj({name,age,...d}){
console.log(d); // {sex:33,go:false}
}
testObj({
name: 'xx',
sex: 33,
go: false,
age: 18
})
扩展运算符
和上面提到的 rest参数 写法是一模一样,但功能完全不一样。一个收(rest参数),一个放(扩展运算符)
扩展数组
function test(){
var arr = [1, 2, 3];
console.log(...arr); // 其实就是把数组的中括号[]去掉
}
扩展字符串 (不推荐)
function test(){
var str = 'abcd';
console.log(...str); // 其实就是把字符串分割成一个一个字符
}
扩展对象
function test(){
var obj = {
name: 'wt',
age: 18
};
// 其实就是把对象的大括号{}去掉
var newObj = {
sex: 'men',
...obj
};
}
注意
使用扩展运算符来创建对象,是一种浅复制,最外层属性是非复杂类型,将完全复制,如果是复杂类型(对象、数组、函数),将和原本对象共用一套数据(说白就是你改,我也变,非深度复杂),代码如下:
const obj = {
name: 'xxx',
info: {
schoolName: 'bsss'
}
}
const obj1 = {
...obj
}
obj.name = '2222'
obj.info.schoolName = 'woshixiaoxue'
console.log(obj1.name); // xxx
console.log(obj1.info.schoolName); // woshixiaoxue
使用场景
1、数组中的值作为函数参数(对象中的值不可作为函数参数)
function test(a, b, c){
console.log(a,b,c);
}
let arr = [10,20,30];
test(...arr);
2、数组合并
let arr1 = [1,2,3],arr2 = [2,3,4];
// 合并,但不会去重
let arr = [...arr1, ...arr2]; // [1,2,3,2,3,4]
3、字符串转数组
let arr = [...'abcd'];
4、对象合并
let obj1 = {
name: 'ww',
age: 18
};
let obj2 = {
name: 'tt',
sex: '男'
};
// 合并并去重,如果有相同KEY,后面替换前面(书写顺序)
let newObj = {...obj2,...obj1};
// es6还提供了另一种合并对象的API
let newObj = Object.assign({}, obj2, obj1);
注意事项:
被合并对象(obj1/obj2)的属性值,如果是引用类型,那么修改被合并对象(obj1/obj2)后,合并对象(newObj)对应的属性值也会跟随变化,如果非引用类型,那就不会跟随变化
箭头函数
使用“箭头”(=>)来简化函数定义
普通函数与箭头函数的区别
1、普通函数有3种定义方式(函数声明、函数表达式、匿名函数),而箭头函数只有2种(函数表达式、匿名函数)
2、普通函数中的this是动态的,而箭头函数中的this是静态的
3、普通函数的this指向调用者,而箭头函数的this指向当前所在环境的this
声明
// 表达式声明
let fn = () => {};
// 匿名函数用法
() => {}
注意点
1、函数参数如果只有一个,可以省略小括号不写
2、如果函数体内只有一个语句,可以省略大括号不写,如果这个语句是return,那么可以连return都省略
3、如果函数体内有多个语句,则不能省略大括号
4、如果函数体只有一个语句,且返回一个对象,不能省略大括号和return
5、箭头函数不能使用arguments,可以使用rest参数替代
// 无参数且只有一个语句
const test1 = () => console.log(123);
// 一个参数且多个语句
const test2 = p => {
let a = 1,b = 2;
return a+b;
};
// 多个参数且只有一个返回语句
const test3 = (a,b) => a+b;
应用场景
回调函数
// 箭头函数很适合做回调函数
setTimeout(()=>{
console.log(123);
}, 3000);
// 数组遍历
let arr = [1,2,3,4];
arr.forEach(item => console.log(item));
this
箭头函数中的
this指向与function函数中的this指向不同,箭头函数的this指向外层的this(可以理解为箭头函数不存在自己的this)
字面量对象中也不存在this,对象属性使用this指向对象所在作用域的this
var obj = {
name:‘bb’,
desc: this.name + "我来了" // 这里的this指代的是window,并非obj对象
go: function(){
console.log(this.name); // obj.go()这里this指向obj对象 (推荐)
},
come: () => {
console.log(this.name); // obj.come()这里this指向外层window
}
}
let btn = document.getElementById("#send");
btn.onclick = function(){
setTimeout(function(){
console.log(this); // this指向window
}, 2000);
setTimeout(()=>{
console.log(this); // this指向btn (推荐)
}, 3000);
}
Promise
相比起传统方案(回调函数),是更合理更强大的异步编程解决方案!
Promise是一个对象,更像一个容器,保存异步操作
作用
解决异步编程中的回调地狱问题,异步代码同步化
三种状态
1、pending 初始状态,既不是成功,也不是失败
2、fulfilled 操作成功
3、rejected 操作失败
特点
1、Promise对象不受外界影响,Promise对象代表一个异步操作。只有异步操作的结果可以决定Promise状态
2、一旦状态改变就不会再变,Promise对象的状态改变只有2种情况:
第一、pending变为fulfilled
第二、pending变为rejected
状态一旦凝固,就不会再改变了
状态发生改变后,你再对Promise对象添加回调函数,回调函数也会立刻得到这个结果。
和事件完全不同
语法
const p = new Promise(function(resolve, reject){
if("操作成功"){
resolve(); // pending --> fulfiled 成功
}else{
reject(); // pending --> rejected 失败
}
});
p.then(d => {}).catch(err => {});
数组
reduce 遍历求和
语法
let arr = [1,2,3];
let res = arr.reduce(function(pre, current, index, arr){
// 遍历arr数组,每遍历一个元素,就执行一遍这个函数
// pre指的是上一次执行这个函数的返回值
// 如果遍历第一次执行的话,有initValue的话,pre就是initValue(从0开始遍历)
// 否则pre就是下标为0的值(从1开始遍历)
// current当前遍历的元素
return 10;
}, initValue);
console.log(res) // 最后一次遍历所return的值
应用情景
求和、求乘积
const arr = [1,4,5,6,7];
// 求和
let addRes = arr.reduce(function(pre, current, index, arr){
return pre+current;
}, 0);
// 求乘积
let mulRes = arr.reduce(function(pre, current, index, arr){
return pre*current;
}, 1);