ES6
ES6 ECMAScipt 第六版
ECMAScript 是js的一套标准化设置
let,const
let const 是es6新增的语法
作用:用来声明变量的 var作用一样
区别:
let同一作用域内可以重新赋值 但不能重复声明
const声明的是常量 声明后 不能重新声明或重新赋值
特点
1.let声明的常量 只在let所在的作用域内起作用(变量绑定)
var a = 123;
if(true){
a = 456;
console.log(a); // 报错
let a;
}
(1) 作用域问题
通常情况下 如果需要一个变量在规定的作用域内
可以使用function关键自搭建一个作用域
(function(){
var a = 1;
}())
console.log(a);
es6中 let只要存在{} 就可以形成块级作用域 if结构 for循环结构 都有{}
{
let b = 1
}
console.log(b);
(2) 变量提升问题
在if结构里使用var声明 {}不能形成块级作用域 那么if结构里的变量声明 就会覆盖全局变量
解决办法:在if结构里使用let声明变量 在if结构里形成块级作用域
var time = new Data();
function fn(){
console.log(time);
if(false){
// var time = "...";
let time = "...";
}
}
(3) for循环中 循环变量泄露为全局变量的问题
// 使用var定义变量
// 定时器为异步操作 当执行定时器时 循环结束变量i=5
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
})
}
console.log("wrap");
// wrap
// 5 5 5 5 5
解决:
使用自执行函数创建块级作用域 但是外部可以访问到循环变量i
for (var i = 0; i < 5; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
})
}(i))
}
console.log("wrap");
console.log(i); // 5
// wrap
// 0 1 2 3 4
使用let定义块级作用域
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
})
}
console.log("wrap");
// console.log(i); // 报错
// wrap
// 0 1 2 3 4
js语言特性 : 单线程 同一时间内只做一件事
任务队列 : 所有的任务都要排队 前面一个执行完 才会执行后面的任务
事件循环 : 主线程会不断循环的去任务队列里读取任务(Event Loop)
当遇到异步任务 或某个事件被触发 那么相应的回调函数或函数体就会被拉到主线程里面去执行
数组解构赋值
基本用法
在ES6中 按照一定的模式 从数组中提取数值 对对应的变量赋值的操作 就叫结构赋值
本质上 解构赋值就是模式匹配
// 解构赋值
var [a,b,c] = [1,2,3]
console.log(a,b,c); // 1,2,3
1.如果要解构成功 那么就必须保证两边模式完全一样
var [a,b,c] = [1,[2],{name:"lucy"}];
console.log(a,b,c); // 1,[2],{name:"lucy"}
2.如果解构不成功 那么变量的值就等于undefined
let [r] = [];
console.log(r); // undefined 就像var声明变量不赋值
3.不完全解构情况下 也可以进行解构 赋值 只是后面放数值的数组有多的数字
不完全解构 指的是等号左右两边模式一样 但是只匹配到右边的一部分数据
let[x,y] = [1,2,3]
console.log(x,y); // 1,2
4.如果等号右边不是数组 那么就会报错
let [m] = 1;
let [m] = false;
let [m] = null;
let [m] = {};
console.log(m); // {} is not iterable 因为{}不能使用下标进行迭代
注意点
1.解构赋值是允许设置默认值的 但是要启动默认值 这个变量的值就必须严格等于undefined
let[a,b] = [1,2]
let[a,b = "abc"] = [1,2]
console.log(a,b); // 1,2
let[a,b = "abc"] = [1]
console.log(a,b); // 1,"abc"
let [a, b = "abc"] = [1,null];
console.log(a,b); // 1 "null"
let [a, b = "abc"] = [1,undefined];
console.log(a,b); // 1 "abc"
let [a, b = "abc"] = [1,""];
console.log(a,b); // 1 ""
2.如果设置的默认值是一个表达式 那么这个表达式只是在需要启动默认值是才会运算
function fn() {
console.log(123);
// return 123;
}
let [s = fn()] = [12];
console.log(s); // 12;
let [s = fn()] = [];
console.log(s); // undefined 这里的undefined是fn调用后返回值undefined
3.默认值可以使用其他变量 但是前提是赋值的这个变量必须是提前声明过的
let d = 3;
let [x = d] = [];
console.log(x); // 3
let [x = 2, y = x] = [];
console.log(x,y); // 2,2
let [x = y, y = 2] = [];
console.log(x,y); // 报错
// ||
let x = y;
let y = 2;
let [x = 2,y = x] = [8];
console.log(x,y); // 8,8
扩展运算符(…)
使用扩展运算符 就是将一个数组转为用逗号分隔的参数序列。
// 直接赋值 修改了原数组 arr1里的值也会发生改变 因为他们指向同一块内存空间
var arr = [100, 200, 300];
var arr1 = arr;
arr[1] = 20;
console.log(arr); // [100, 20, 300]
console.log(arr1); // [100, 20, 300]
// 使用扩展运算符 不会影响新数组
var arr = [100, 200, 300];
var arr2 = [...arr]
arr[0] = 10;
console.log(arr); // [10, 200, 300]
console.log(arr2); // [100,200,300]
扩展运算符的作用
1.数组合并
concat方法 扩展运算符 都是浅拷贝 如果修改了原数组的指向 就会同步到新数组 就是浅拷贝
var arr1 = [1,2,3];
var arr2 = [10,20,30];
// ES5 的合并数组
var arrCon = arr1.concat(arr2);
console.log(arrCon); // [1, 2, 3, 10, 20, 30]
// ES6 的合并数组
var arrSp = [...arr1,...arr2];
console.log(arrSp); // [1, 2, 3, 10, 20, 30]
// 这里证明了合并后的数组和合并前的数组指向同一片内存空间(第一个数组) 所以证明了都是浅拷贝
console.log(arrCon[0] === arr1[0]); // true
console.log(arrSp[0] === arr1[0]); // true
// 修改原数组后 会同步反映到新数组
arr1[2] = 33;
console.log(arrCon); // [1, 2, 33, 10, 20, 30]
console.log(arrSp); // [1, 2, 33, 10, 20, 30]
2.数组的拷贝
// 浅拷贝 修改原数组 会同步反映到新数组
var arr = [1, [2], { name: "zhang" }];
var arrSp = [...arr];
arr[2].name = "张三";
console.log(arrSp); // name = "张三"
// 深拷贝 修改原数组 不会影响新数组
var arr = [1, [2], { name: "zhang" }];
var str = JSON.stringify(arr);
var arr2 = JSON.parse(str);
arr[2].name = "张三";
console.log(arr2); // name = "zhang"
3.与解构赋值结合
如果是普通变量 那么就按照模式赋值 如果使用了扩展运算符 那么就是后面的值依次放入数组
var [s, ...t] = [1, 2, 3, 4, 5];
console.log(s, t); // 1 [2,3,4,5]
// 如果等号右边是空数组 那么仅仅是声明 变量值是undefined 数组是[]
var [s, ...t] = [];
console.log(s, t); // undefined []
// 如果扩展运算符用于数组赋值 只能放在参数最后
// let [a, ...b, c] = [1, 2, 3, 4, 5]; // 报错 Rest element must be last element
4.扩展运算符可以将字符串转成真正的数组
// ES5
var str = "hello";
console.log(str.split("")); // ["h", "e", "l", "l", "o"]
// ES6
console.log([..."hello"]); // ["h", "e", "l", "l", "o"]
**注意:**只有函数调用时 扩展运算符才可以放在圆括号里 否则就会报错
var arr = [100, 200, 300];
// (...arr1); // 报错
// console.log([...arr]);
// console.log((...arr)); // 报错
console.log(...arr); // 100 200 300
对象的扩展
1.对象的简单赋值
ES6在{} 允许直接写入变量名 解析时 变量名作为属性名 变量值作为属性值
var name = "lucy";
var age = 18;
var obj2 = {
name, // 属性名 属性值相同时可以简写
age,
study() { // 函数的简写
console.log("好好学习");
}
}
console.log(obj2);
obj2.study();
// 示例
function count(x, y) {
x = Math.pow(x, 2);
y = Math.sqrt(y);
// return {
// x: x,
// y: y
// }
return { x, y }
}
var res = count(3, 9)
console.log(res); // {x: 9, y: 3}
2.super关键字
super关键字 指的是 指向当前对象的原型对象 _ proto _
注意点:
super关键字只能在对象的方法中使用 在其他地方使用就会报错
super关键字目前只能在对象方法的简写方式里去用 常规写法就会报错
var stu = {
name: "张三",
age: 12,
// super不能用在对象里
// type:super.type, // 'super' keyword unexpected here
// super关键字目前只能在对象的方法简写时才能用
// learn:function(){
// console.log("我是" + super.type + "我爱学习");
// },
learn() {
console.log("我是" + super.type + "我爱学习");
}
}
stu.__proto__ = {
type: "学生",
run: function () {
console.log("跑步");
}
}
setPrototypeOf() 方法 :设置对象的原型
上面案例可以还可以这样写
var stu = {
name: "张三",
age: 12,
learn() {
console.log("我是" + super.type + "我爱学习");
}
}
var proto = {
type: "学生"
}
Object.setPrototypeOf(stu, proto);
stu.learn(); // 我是学生我爱学习
setPrototypeOf() 方法
Object.setPrototypeOf
方法的作用与__proto__
相同,用来设置一个对象的原型对象(prototype),返回参数对象本身
// 父亲类
function Father(){
this.name = "父亲";
this.age = "50";
}
// 父亲类原型上的方法
Father.prototype.type ="人类";
// 自定义原型对象
var proto = {
job:"教师"
}
// 实例化对象
var f1 = new Father();
// 使用setPrototypeOf方法替换掉了f1的原型
Object.setPrototypeOf(f1,proto);
console.log(f1.job); // 教师
// f1的原型对象 已经被修改 所以不再拥有Father.prototype里面的属性 所以访问的type是undefined
console.log(f1.type); // undefined
var f2 = new Father();
// 因为f1的原型对象被重新设置 所以f1可以访问到 job属性
// 但是f2依然指向Father.prototype 访问不到proto对象的属性
console.log(f2.job); // undefined
console.log(f2.type); // 人类
3.属性名表达式
ES6 允许字面量定义对象时,用表达式作为对象的属性名,即把表达式放在方括号内。
var n1 = "name"
var n2 = "age"
var n3 = "hobby"
function getGender(){
return "gender"
}
var obj = {
[n1] : "学生",
[n2] : 20,
[n3] : function(){
console.log("爱好");
},
[n3](){
console.log(this[n1] + "---" + this[n2]);
},
[getGender()]:"男"
}
obj[n3](); // 学生 --- 20
console.log(obj[getGender()]); // 男
注意:
1.属性名表达式与简洁表示法,不能同时使用,会报错。
// 报错
const foo = 'bar';
const bar = 'abc';
const baz = { [foo] };
// 正确
const foo = 'bar';
const baz = { [foo]: 'abc'};
2.属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]
const A = { a: 1 };
const B = { b: 2 };
const myObject = {
[A]: 'valueA',
[B]: 'valueB'
};
console.log(myObject); // {[object Object]: "valueB"}
上面代码中,A和B得到的都是[object Object]
,所以B会把A覆盖掉,
所以myObject最后只有一个[object Object]属性。
Object对象新增的方法
1.Object.is() 严格判断
这个方法和js的严格判断(===)用法基本一致 只是在对于-0 和 +0 以及NaN的判断上做了改善
console.log(-0 === +0); // true
console.log(NaN === NaN); // false
console.log(Object.is(-0, +0)); // false
console.log(Object.is(NaN, NaN)); // true
2.Object.assign() 合并对象
参数1 目标对象 参数2… 需要合并的对象
特点
1.返回值是传入的第一个对象 会把所有的对象合并上去再返回
var userName = { "name": "张三" }
var age = { "age": 18 }
var gender = { "gender": "男" }
console.log(Object.assign(age,userName,gender));// {age: 18, name: "张三", gender: "男"}
console.log(objAssign === age); // true
console.log(objAssign === userName); // false
console.log(objAssign === gender); // false
2.第一个参数必须是对象 如果不是对象 就会转成对象
3.第一个参数如果是undefined 或 null 就不能转成对象
// 这里的字符串转成了包装类对象
console.log(Object.assign("", userName, age, gender));
// String {"", name: "张三", age: 18, gender: "男", length: 0}
console.log(Object.assign({}, userName, age, gender));
// {name: "张三", age: 18, gender: "男"}
// 第一个参数如果是undefined 或 null 就不能转成对象
// var stu = Object.assign(undefined,userName,age,gender) // 报错
// var stu = Object.assign(null,userName,age,gender) // 报错
4.如果在需要合并的多个对象里面有同名属性 后面的就会覆盖
var stu1 = {"name":"张三"}
var stu2 = {"name":"李四","age":18}
var stu3 = {"gender":"男","age":20}
console.log(Object.assign(stu1,stu2,stu3)); // {name: "李四", age: 20, gender: "男"}
5.如果undifined 和 null 不是第一个参数 就不会报错
var stu = {name : "张三"}
console.log(Object.assign(stu, undefined)); // {name: "张三"}
console.log(Object.assign(stu, null)); // {name: "张三"}
console.log(Object.assign(stu, {})); // {name: "张三"}
6.assign方法是浅拷贝
var stu = {name : "张三"}
console.log(Object.assign({}, stu) === stu); // false
var a = {
b: { c: 1 }
}
var s = Object.assign({}, a)
console.log(s.b.c); // 1
// 修改原对象的值 新对象也会改变
a.b.c = 2;
console.log(s.b.c); // 2Object,setPrototypeOf 给一个对象设置原型对象
3.Object.setPrototypeOf() getPrototypeOf()
Object,setPrototypeOf 给一个对象设置原型对象
参数1 : 目标对象 参数2 : 原型对象
Object.getPrototypeOf() 获取对象原型的方法
参数1 : 目标对象
var obj = {
n1: 10,
n2: 20
}
var obj1 = {
n3: 30,
n4: 40
}
var a = Object.setPrototypeOf(obj, obj1);
// setPrototypeOf 返回参数1
// 参数1的__proto__指向了参数2
console.log(a); // {n1: 10, n2: 20}
console.log(a === obj); // true
console.log(obj.__proto__); // {n3: 30, n4: 40}
// getPrototypeOf 返回参数的__proto__
console.log(Object.getPrototypeOf(obj)); // {n3: 30, n4: 40}
4 .Object.values() Object.keys()
Object.keys() 是将对象所有的属性名遍历 然后返回一个数组
Object.values() 是将对象所有的属性值遍历 然后返回一个数组
var stu = {
name: "张三",
age: 18,
work: "学生"
}
console.log(Object.keys(stu)); // ["name", "age", "work"]
console.log(Object.values(stu)); // ["张三", 18, "学生"]
Class关键字
传统的构造函数的缺点 :
- 构造函数和原型方法属性分离 不便于维护 降低了可读性
- 原型对象的成员可以遍历
- 默认情况下构造函数也是可以被当做普通函数来调用的 所以 功能性不明显
function Person(name, sex) {
this.name = name;
this.sex = sex;
return { name, sex }
}
// 给Person原型中添加toString方法
Person.prototype.toString = function () {
return this.name + "," + this.sex
}
var p1 = new Person("张三", "男")
// console.log(new p1.toString()); // Person.toString {}
var p2 = new Person("李四", "女")
// console.log(p2.toString()); // 李四,女
console.log(Person.prototype);
// 遍历对象原型中的属性
for (let attr in Person.prototype) {
console.log(attr); // toString
}
// 当做普通函数调用
console.log(Person()); // {name: undefined, sex: undefined}
class基本用法
- class就是一个语法糖 本质就是一个函数 就是使用ES5里面的函数封装的
- 在类里面去定义方法的时候 可以省略function
- class类的数据类型是function
// 定义一个Person类
class Person {
// 构造方法
constructor(name, sex) {
// this关键字则代表实例对象
this.name = name;
this.sex = sex;
}
// 定义一个toString方法
toString() {
return this.name + "," + this.sex
}
}
var p1 = new Person("张三", "男");
console.log(p1.toString()); // 张三,男
console.log(typeof Person); // function
class类使用注意点
1.类是不可枚举的
for (let k in Person) {
console.log(k);
}
2.类的用法很let和const一样 都有暂时性死区 必须先定义 再使用
new Person(); // 报错
class Person{
}
3.类里面的方法不能做为构造函数来使用
var s1 = new p2.toString();
console.log(s1); // 报错
4.类使用的时候 必须配合new关键字进行使用 否则就会报错
Person(); // 报错