目录
ES6基础入门之let、const
变量声明
var声明
var name='hiyori'
直接使用
window.location.href===location.href
说明:这种不加var声明的定义方式,相当于在全局对象window上挂了一个属性,会造成全局对象污染 ,不建议这么做
let和var的主要区别
- let声明的变量只在当前(块级)作用域内有效
- let声明的变量不能被重复声明
- let不存在变量提升
ES6之前的作用域
全局作用域、函数作用域、eval作用域
块级作用域
- 通俗的讲,就是一堆花括号中的区域{...}
- 块级作用域可以嵌套
/* let声明的变量只在当前(块级)作用域内有效 */
//example1
{
var a = 1;
let b = 2;
}
console.log(a); //打印a
console.log(b); //出了块级作用域,报错信息为未定义
//example2
{
let a = 1;
{
console.log(a); //这里属于a的作用范围,打印1
let b = 2;
}
console.log(b); //这里不属于b的作用范围,报错
}
//example3
for (let i = 0; i < 3; i++) {
};
console.log(i); //报错
for (var i = 0; i < 3; i++) {
console.log(i);
};
console.log(i); //打印3,var定义的i并不会被释放,因为初始化时定义的i相当于是在循环语句的作用域之外定义的
/* 使用let或者const声明的变量不能被重新声明 */
var dad = '我是爸爸!';
var dad;
console.log(dad); //重新声明,没有被赋值,该变量还是'我是爸爸'
var dad = '我才是爸爸!';
console.log(dad); //修改为新的值
let son = '我是儿子';
let son; //报错,提示son已被声明过
let son = '我才是儿子'; //报错,提示son已被声明过
/* let不存在变量提升 */
console.log(dad); //undefined
var dad='我是爸爸';
//以上情形就是因为变量提升
//预解析时会先解析带有var关键字的变量,并赋值为undefined,执行代码时才赋真正的值
//相当于
var dad; //变量声明被拉到最前面,而后赋值
console.log(dad);
dad='我是爸爸';
console.log(dad); //报错,提示未定义该变量
let dad='我是爸爸';
//用let定义的变量不会把声明拉到最前面,因此不具有变量提升
//暂存死区
var monkey = '我是美猴王';
{
console.log(monkey); //我是..
var monkey = '我觉得我还能再抢救一下!';
}
console.log(monkey); //我觉得..
let monkey = '我是美猴王';
{
console.log(monkey); //报错
let monkey = '我觉得我还能再抢救一下!';
}
console.log(monkey);
let面试常见例子
q: 生成十个按钮 每个按点击的时候弹出1 - 10
var i = 0;
for (i = 1; i <= 10; i ++) {
(function(i) {
var btn = document.createElement('button');
btn.innerText = i;
btn.onclick = function() {
alert(i)
};
document.body.appendChild(btn);
})(i);
}
//在外面加上自执行匿名函数,就会形成独立的函数作用域,i作为参数传进来,在作用域中获得的就是实时更新的i
//如果没有定义一个自执行的function,那么这里每次点击弹出的都是11
//因为发生点击事件后要去寻找i,本层找不到就会到上一层去找,上一层的i在循环结束后值为11
//因此事件中获取到的i的值也是11
//使用let定义,可以实现和添加自执行函数一样的效果
for (let i = 1; i <= 10; i ++) {
var btn = document.createElement('button');
btn.innerText = i;
btn.onclick = function() {
alert(i)
};
document.body.appendChild(btn);
}
const
常量——不可改变的量
和声明变量一样,基本只是关键字的区别
常量必须在声明的时候赋值,否则报错:Missing initiaizer in const declaration
const a; //不赋值,会报错
var b=2;
let c;
const与let类似的特性
不能重复声明
不存在变量提升
只在当前(块级)作用域内有效
常量不可变
一旦声明常量,就不能再改变
但常量为引用类型的时候,不能保证不可变
const NAME='xh';
NAME='xm'; //报错,提示常量不能被修改
说明:const只能保证引用常量的地址不变,不能保证它里面的值不变。
//对象
const xiaoming={
age:14;
name:'xm';
}
xiaoming.age=22; //允许修改
xiaoming={}; //报错,不允许被修改,因为相当于把一个新的地址给了xiaoming这个常引用对象
//数组
const ARR=[];
ARR.push(1); //允许添加
ARR=[]; //报错,不允许被修改,还是相当于把一个新的地址给了ARR这个常引用数组
怎么解决引用类型的常量可以被修改的问题?
Object.freeze()
const xiaoming = {
age: 14,
name: '小明'
};
Object.freeze(xiaoming); //xiaoming对象中的值也不允许被修改了
console.log(xiaoming);
xiaoming.age = 22; //不报错,但不会发生改变
xiaoming.dd = 11; //不报错,但不会发生改变
console.log(xiaoming);
const ARR = [];
Object.freeze(ARR); //ARR数组中的值也不允许被修改了
ARR.push(1); //报错,提示不能被扩展
console.log(ARR);
ES6之前怎么声明常量?
用var声明,假装是常量
var BASE_COLOR = '#ff0000';
Object.defineProperty();
var CST = {a: 1};
Object.defineProperty(CST, 'a', {
writable: false
});
//writable:只读,不能被修改
Object.seal(CST); //只能防止被扩展
//Object.seal+writable:false才能达到freeze()的效果
自行封装一个freeze()
// 1. 遍历属性和方法
// 2. 修改遍历到的属性的描述
// 3. Object.seal()
Object.defineProperty(Object, 'freezePolyfill', {
value: function(obj) {
var i;
for (i in obj) {
if (obj.hasOwnProperty(i)) {
Object.defineProperty(obj, i, {
writable: false //每个属性都不能被修改
});
}
}
Object.seal(obj); //整个对象不能被扩展
}
});
const xiaoming = {
age: 14,
name: '小明',
obj: {
a: 1
}
};
Object.freezePolyfill(xiaoming);
ES6变量的解构赋值
解构赋值语法是一个JavaScript表达式,这使得可以将值从数组或属性从对象提取到不同的变量中
数组的解构赋值
数组的解构赋值
// 数组的解构赋值
const arr = [1, 2, 3, 4];
let [a, b, c, d] = arr;
//会根据元素在数组中所在的位置一一对应匹配,a=1,b=2……
更复杂的匹配规则
// 更复杂的匹配规则
const arr = ['a', 'b', ['c', 'd', ['e', 'f', 'g']]];
const [ , b] = arr; //只返回b
const [ , , g] = ['e', 'f', 'g'] //只返回g
const [ , , [ , , g]] = ['c', 'd', ['e', 'f', 'g']]; //只返回g
const [ , , [ , , [ , , g]]] = arr; //只返回g
扩展运算符 ...
// 扩展运算符 ...
const arr1 = [1, 2, 3];
const arr2 = ['a', 'b'];
const arr3 = ['zz', 1];
const arr4 = [...arr1, ...arr2, ...arr3]; //可以把arr1、arr2、arr3合并起来
const arr = [1, 2, 3, 4, 5, 6];
const [a, b, ...c] = arr; //会把3,4,5,6作为一个数组赋给c
const [a, b, ...c,d] = arr; //错误
注意:带扩展运算符的变量后面不能再有其他变量,因为扩展运算符就是用来取最后几个元素进行合并的
默认值
// 默认值
const arr = [1, null, undefined];
const [a, b = 2, c, d = 'aaa'] = arr; //设置默认值,当匹配到undefined时,就使用默认值
//a=1,b=null,c=undefined,d='aaa'
说明:解构赋值时没有匹配到的就被赋值为undefined
交换变量
// 交换变量
let a = 20;
let b = 10;
//方法①
let temp;
temp = a;
a = b;
b = temp;
//方法②,利用解构赋值
[a, b] = [b, a];
接收多个 函数返回值
// 接收多个 函数返回值
function getUserInfo(id) {
// .. ajax
return [
true,
{
name: '小明',
gender: '女',
id: id
},
'请求成功'
];
};
const [status, data, msg] = getUserInfo(123);
//status匹配到函数返回值第一项布尔值,data匹配到函数返回值第二项对象,msg匹配到函数返回值第三项文字
对象的解构赋值
对象的解构赋值与数组的解构赋值相似
等号左右两边都为对象结构
const {a,b}={a:1,b:2};
左边的{}中为需要赋值的变量
右边为需要解构的对象
对象解构赋值的用法
// 对象的解构赋值
const obj = {
saber: '阿尔托利亚',
archer: '卫宫'
};
const { saber, archer1 } = obj; //archer属性名不匹配,archer1的值是undefined
注意:对象的解构赋值要求不同对象的属性名要一致,才能匹配。因为数组是有序的,而对象是无序的,必须用属性名去匹配,而不是用位置去匹配。
稍微复杂的解构条件
// 稍微复杂的解构条件
const player = {
nickname: '感情的戏∫我没演技∆',
master: '东海龙王',
skill: [{
skillName: '龙吟',
mp: '100',
time: 6000
},{
skillName: '龙卷雨击',
mp: '400',
time: 3000
},{
skillName: '龙腾',
mp: '900',
time: 60000
}]
};
const { nickname } = player; //返回感情的戏∫我没演技∆
const { master } = player; //返回东海龙王
const { skill: [ skill1, { skillName }, { skillName: sklName } ] } = player;
//保持格式一致,就可以解构skill里的属性
//对数组进行赋值时变量名可以随便起,skill1获得skill数组的第一项,skillName获得数组第二项中的skillName龙卷雨击
//这时如果想要获得skill数组第三项的skillName再次使用{skillName}是不行的,因为const声明的变量中已经有skillName了
//我们需要另外起一个名字,只要在属性名后面加上:新名字,就可以解决这个问题,意思是把skillName中的值赋给sklName
const { skill } = player; //返回一个数组
const [ skill1 ] = skill; //skill是一个数组,skill1是数组元素的第一项——龙吟,100,6000
const { skill:[skill1] }=player; //与上一行完全一样,但不会取到skill
扩展运算符 ...
// 结合扩展运算符
const obj = {
saber: '阿尔托利亚',
archer: '卫宫',
lancer: '瑟坦达'
};
const { saber, ...oth } = obj; //会把除了saber以外的剩下所有项赋值给oth
//合并对象
const obj1 = {
archer: '卫宫',
lancer: '瑟坦达'
}
const obj = {
saber: '阿尔托利亚',
...obj1, //会把obj1所有属性合并成一个对象
};
如何对已经申明了的变量进行对象的解构赋值
// 如何对已经申明了的变量进行对象的解构赋值
let age;
const obj = {
name: '小明',
age: 22
};
({ age } = obj);
//{age}外面如果不加(),会被编译器理解成块级作用域,然后报错。
//最好的方法是带上变量声明
const obj = {
name: '小明',
age: 22
};
let {age}=obj;
默认值
// 默认值
let girlfriend = {
name: '小红',
age: undefined,
};
let { name, age = 24, hobby = ['学习'] } = girlfriend;
//name='小红',age=24
对象的解构赋值的主要用途
提取对象属性
// 提取对象属性
const { name, hobby: [ hobby1 ], hobby } = { //把hobby值赋给hobby1,再定义一个hobby就获取到对象的hobby属性值
name: '小红',
hobby: ['学习']
};
使用对象传入乱序的函数参数
function AJAX({ //传参时解构赋值,可以不按顺序赋值,如果type没有设置默认为get
url,
data,
type = 'get'
}) {
// var type = option.type || 'get';
// console.log(option);
console.log(type);
};
AJAX({
data: {a: 1},
url: '/getinfo',
});
获取多个函数返回值
// 获取多个 函数返回值
function getUserInfo(uid) {
// ...ajax
return {
status: true,
data: { name: '小红' },
msg: '请求成功'
};
};
const { status, data, msg: message } = getUserInfo(123);
字符串的解构赋值
// 字符串的结构赋值
const str = 'I am the bone of my sword'; // 我是剑骨头
const [ a, b ,c, ...oth ] = str;
//a=I,b= ,c=a,oth=m the bone of my sword,注意这里oth是一个数组,每个字符是一个数组元素
const [ ...spStr1 ] = str; //spStr1是一个数组,每个数组元素是str的一个字符
const spStr2 = str.split(''); //同上一句效果一样
const spStr3 = [ ...str ]; //同上一句效果一样
// 提取属性
const { length, split } = str;
数值与布尔值的解构赋值
// 数值与布尔值的解构赋值
const { valueOf: vo } = 1;
const { toString: ts } = false;
函数参数的解构赋值
// 函数参数的解构赋值
function swap([x, y]) {
return [y, x];
};
let arr = [1, 2];
arr = swap(arr);
//定义一个Computer的构造函数
function Computer({
cpu,
memory,
software = ['ie6'],
OS = 'windows 3.5'
}) {
console.log(cpu);
console.log(memory);
console.log(software);
console.log(OS);
};
//声明一个Computer对象
new Computer({
memory: '128G',
cpu: '80286',
OS: 'windows 10'
});
ES6扩展
字符串扩展
模板字符串
// 模版字符串
// 'asdffdsa'
// "asdfasdf"
// `asdfasdffsa`
const xiaoming = {
name: 'xiaoming',
age: 14,
say1: function() {
console.log('我叫' + this.name.toUpperCase() + ', 我今年' + this.age + '岁!');
},
say2: function() {
console.log(`我叫${ `Mr.${ this.name.toUpperCase() }` }, 我今年${ this.age }岁!`); //允许嵌套
}
}
xiaoming.say1();
xiaoming.say2();
//运行结果是一样的
说明:模板字符串不需要用+号拼接字符串,可以用${ }把我们要拼接值直接放到 `` 中,且可以自动换行
模板字符串在数据渲染中的应用
const getImoocCourseList = function() {
// ajax
return {
status: true,
msg: '获取成功',
data: [{
id: 1,
title: 'Vue 入门',
date: 'xxxx-01-09'
}, {
id: 2,
title: 'ES6 入门',
date: 'xxxx-01-10'
}, {
id: 3,
title: 'React入门',
date: 'xxxx-01-11'
}]
}
};
const { data: listData, status, msg } = getImoocCourseList(); //解构赋值,给获得到的data属性值起名为listData
function foo(val) {
return val.replace('xxxx', 'xoxo');
}
if (status) {
let arr = [];
listData.forEach(function({ date, title }) { //遍历listData中的每一项元素,每次都调用函数
//在模板字符串前,我们使用这种方法,非常容易出错
// arr.push(
// '<li>\
// <span>' + title + '</span>' +
// '<span>' + date + '</span>' +
// '</li>'
// );
//使用模板字符串
arr.push(
`<li>
<span>${ `课程名: ${ title }` }</span>
<span>${ foo(date) }</span>
</li>`
);
});
let ul = document.createElement('ul');
ul.innerHTML = arr.join(''); //拼接arr中的所有元素
document.body.appendChild(ul);
} else {
alert(msg);
}
字符串部分新的方法
padStart、padEnd
在字符串前后填充另一个字符串
// padStart padEnd
{
let str = 'i';
let str1 = str.padStart(5, 'mooc'); //5指定字符串长度,mooc是填充的字符串
console.log(str1); //在str之前填充字符串mooc,直到满足指定的长度5,打印mooci
let str2 = str.padEnd(5, 'mooc'); //5指定字符串长度,mooc是填充的字符串
console.log(str2); //在str之后填充字符串mooc,直到满足指定的长度5,打印imooc
}
repeat
重复字符串
// repeat
{
console.log('i',repeat(10)); //iiiiiiiiii
//自行实现repeat方法
function repeat(str, num) {
return new Array(num + 1).join(str);
}
console.log(repeat('s', 3)); //sss
}
注意:repeat的参数不能是小于-1的负数,但可以是大于-1的负数,如-0.88自动化为0,正小数也会取整,如2.33化为2
startsWith、endsWith
判断字符串是否以某字符串开头或结尾
// startsWith endsWith
{
const str = 'A promise is a promsie';
console.log(str.startsWith('B')); //false
console.log(str.startsWith('A pro')); //true
console.log(str.endsWith('promsie')); //true
console.log(str.endsWith('A')); //false
}
includes
判断字符串中是否包含某个字符串
// includes
{
const str = 'A promise is a promise';
// if (str.indexOf('promise') !== -1) {
if (~str.indexOf('promise')) { //~x=-(x+1)
console.log('存在1');
}
if (str.includes('a promise')) {
console.log('存在2'); //打印存在2,说明if条件成真
}
}
for-of
ES6之前遍历字符串的方式:
使用for循环
// 使用for循环
for (var i = 0, len = str.length; i < len; i ++) {
console.log(str[i]);
console.log(str.charAt(i));
}
转成数组后遍历
// 转成数组后遍历
var oStr = Array.prototype.slice.call(str); //调用Array对象原型中的slice方法
var oStr = str.split(''); //与上一条语句等效
const oStr = [...str]; //与上一条语句等效
const [...oStr] = str; //与上一条语句等效
oStr.forEach(function(word) {
console.log(word);
});
console.log(oStr);
有时候遍历是为了操作字符串
// 对全是英文的字符串中的大写字符加密 A -> 100 B -> 99。。。
const map = {A: '100', B: '99', C: '98', D: '97', E: '96', F: '95', G: '94', H: '93',
I: '92', J: '91', K: '90', L: '89', M: '88', N: '87', O: '86', P: '85', Q: '84', R: '83',
S: '82', T: '81', U: '80', V: '79',W: '78',X: '77',Y: '76', Z: '75'};
const words = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
oStr.forEach(function(word, index) {
if (words.includes(word)) oStr[index] = map[word];
//若当前遍历到的字符word包含在字符串words中,就到map取出word对应的字符串放到oStr数组的第index位
});
console.log(oStr.join('')); //把数组再拼接成字符串
使用for-of遍历字符串
// 使用for-of遍历字符串
for (let word of str) { //word是str中的每个元素
console.log(word);
}
let newStr = '';
for (let word of str) { //word是str中的每个元素
if (words.includes(word)) newStr += map[word];
}
console.log(newStr)
//用for-of不需要将字符串转成数组进行操作后再转回字符串
正则扩展(u、y修饰符)
Unicode表示法
Unicode是一项标准包括字符集、编码方案等。它是为了解决传统的字符编码方案的局限而产生的,为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
// 🐶 \u1f436 unicode码(点)。emoji
console.log('\u1f436'); //浏览器一般只能解析0000-ffff范围的Unicode码,这里打印一个乱码
console.log('\u{1f436}'); //ES6新增特性,加上{}就能正常显示小狗emoji
codePointAt 获取字符串中对应字符的一个码点
'🐶'.codePointAt(0); //参数为字符串中字符的索引值
at 根据下标取字符
'🐶abc'.at(0) 🐶 //索引为0,取到小狗emoji
ES6正则表达式构造函数的变化
const regexp1 = /^a/g;
const regexp2 = new RegExp('^a', 'g');
const regexp3 = new RegExp(/a/g);
const regexp4 = new RegExp(/a/);
console.log('aabbcc'.match(regexp1)); //["a"]
console.log('babbcc'.match(regexp1)); //null
console.log('aabbccaabbaa'.match(regexp3)); //["a","a","a","a","a","a"]
console.log('aabbccaabbaa'.match(regexp4)); //["a"]
// 构造函数的变化
const regexp5 = new RegExp(/a/giuy, 'ig');
//前一个参数的修饰符不管写了什么都会被后一个参数的修饰符覆盖
u修饰符
// u:unicode
console.log(/^\ud83d/.test('\ud83d\udc36'))
// \ud83d\udc36以\ud83d打头,返回true
console.log(/^\ud83d/u.test('\ud83d\udc36'))
// 但我们希望把\ud83d\ud36处理成一个字符,就要加上u字符
// 它就会把四个字节当做一个字符来处理,这样一来就匹配不到了,返回false
y修饰符
// y:粘连修饰符 sticky
const r1 = /imooc/g;
const r2 = /imooc/y;
const str = 'imoocimooc-imooc';
console.log(r1.exec(str)); //匹配第一个imooc
console.log(r1.exec(str)); //匹配第二个imooc
console.log(r1.exec(str)); //匹配第三个imooc
console.log(r1.exec(str)); //null
console.log('-----------------');
console.log(r2.exec(str)); //匹配到第一个imooc
console.log(r2.exec(str)); //匹配到第二个imooc
console.log(r2.exec(str)); //null
说明:y修饰符与g修饰符的不同之处在于:它必须从上一次匹配的索引处继续匹配
数值扩展
新的进制表示法
八进制以0o/0O开头,二进制以0b/0B开头
新的方法与安全数
Number.parseInt、Number.parseFloat
// Number.parseInt Number.parseFloat
console.log(window.parseInt('1.23'));
console.log(parseFloat('1.23')); //与上一句是一样的
//ES6中parseInt()和parseFloat()方法挂在Number对象上
console.log(Number.parseInt(1.23)); //1
console.log(Number.parseFloat(1.23)); //1.23
Number.isNaN、Number.isFinite
说明:一个值如果不等于自身,那么它就是NaN
// Number.isNaN Number.isFinite
// isNaN
console.log(Number.isNaN(NaN));
console.log(Number.isNaN(-NaN));
console.log(Number.isNaN(1));
console.log(Number.isNaN('1'));
console.log(Number.isNaN(true));
function isNaN(value) {
return value !== value;
}
console.log(isNaN(NaN));
console.log(isNaN(-NaN));
console.log(isNaN(1));
console.log(isNaN('1'));
console.log(isNaN(true));
// isFinite 判断一个值是不是有限的
console.log(Number.isFinite(Infinity)); //false
console.log(Number.isFinite(2 / 0)); //false
console.log(Number.isFinite(2 / 4)); //true
console.log(Number.isFinite(1234)); //true
console.log(Number.isFinite('1234')); //false
console.log(Number.isFinite(true)); //false
console.log(Number.isFinite(NaN)); //false
Number.MAX_SAFE_INTEGER、Number.MIN_SAFE_INTEGER、Number.isSafeInteger()
// Number.MAX_SAFE_INTEGER Number.MIN_SAFE_INTEGER 最大安全数和最小安全数
// Number.isSafeInteger(); 判断是否是安全范围内的数
console.log(Number.MAX_SAFE_INTEGER); //9007199254740991
console.log(Number.MIN_SAFE_INTEGER); //-9007199254740991
console.log(Number.isSafeInteger(Number.MAX_SAFE_INTEGER - 1)); //true
console.log(Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1)); //false
幂运算
// 幂运算 n**m就是n的m次幂
let a = (2 ** 10) ** 0; //1
let a = 2 ** 10 ** 0; //2 说明幂运算是右结合的
console.log(a);
函数扩展
默认参数
// 函数参数的默认值
function add(a, b = 999 + a, c = 1) {
//参数等号右边的表达式不能出现等号左边的变量或者在自己之后定义的变量,比如不能出现b=999+b或者b=999+c
console.log(a, b);
}
add(1); //1,1000
function People({ name, age = 38 } = {name: 1}) { //用解构赋值设置默认值
console.log(name, age); //name:3,age:38
};
People({ name: 3 });
剩余参数 ...
注意:剩余参数必须写在参数列表的最后一位
说明:解构赋值中的...是扩展运算符,有扩展作用;而剩余参数中的...有聚合作用
// 结合扩展运算符(剩余参数...)
function sum(...args) { //参数结合扩展运算符,可以使实参在传入的过程中直接转化成数组
let args = Array.prototype.slice.call(arguments); //把类数组arguments对象转成数组
let args = [...arguments]; //效果同上
let [...args] = arguments; //效果同上
console.log(args);
}
sum(1, 2, 321, 4354, 'fdafsd');
function op(type, b, ...nums) { //23,454,3,67,234都传给num变成数组的元素
console.log(type);
console.log(nums);
}
op('sum', 1, 23, 454, 3, 67, 234);
function sum(...numbers) {
return numbers.reduce(function(a, b) { //这个方法来计算总和
return a + b;
}, 0);
}
console.log(sum(1, 2, 3, 4));
箭头函数
箭头函数的写法
// 箭头函数
//以下两句等价
const add = (a, b) => a + b;
const add= function(a, b){
return a + b;
}
//以下两句等价,箭头函数中多行语句需要用{}括起来
const add1 = (a, b) => {
a += 1;
return a + b;
};
const add2 = function(a, b) {
a += 1;
return a + b;
}
//不需要返回值的情况
const pop = arr => void arr.pop();
//函数只有一个参数时可以不带(),比如这里的arr
//这条语句的含义是在函数pop中调用参数arr的pop方法,但不需要返回值,所以只要在语句前面加上void
//如果不加void,arr.pop()这个方法是有返回值的,返回数组出栈元素
//也可以写成多行形式
const pop = arr =>{
arr.pop(); //多行语句由于没有return语句,所以没有返回值,console.log(pop([1, 2, 3]));值为undefined
}
console.log(pop([1, 2, 3])); //函数无返回值时,为undefined,有返回值时为3
箭头函数没有arguments对象
const log = () => { //无参
console.log(arguments); //报错,提示arguments没有被定义
};
log(1, 2, 3);
const log = (...arg) =>{ //可以用省略参数接收,这里被转化成数组
console.log(args); //[1,2,3]
}
log(1, 2, 3);
箭头函数没有自己的this
可以理解成箭头函数的this是自身所处环境的this
const xiaoming = {
name: '小明',
say1: function() {
console.log(this);
},
say2: () => {
console.log(this); //say2()与name同级,name又在window上,所以say2的this指向window
}
}
xiaoming.say1(); //打印对象xiaoming中的内容
xiaoming.say2(); //打印对象window中的内容
const xiaoming = {
name: 'xiaoming',
age: null,
getAge: function() {
let _this = this; //普通函数的this指向调用它的对象,这里获取这个对象
// ...ajax
setTimeout(function() {
_this.age = 14; //如果不这么获取,这个函数的this指向的是window
console.log(_this);
}, 1000);
}
};
xiaoming.getAge();
//用箭头函数实现回调
const xiaoming = {
name: 'xiaoming',
age: null,
getAge: function() {
// ...ajax
setTimeout(() => {
this.age = 14; //箭头函数的this指向该函数定义时所处环境的this,
//这个函数处在外层匿名函数中,而匿名函数的this指向调用它的对象
console.log(this);
}, 1000);
}
};
xiaoming.getAge();
对象的扩展
简洁表示法
// 简洁表示法
const getUserInfo = (id = 1) => {
// AJAX ....
const name = 'xiaoming';
const age = 10;
return { //返回值用解构赋值
name: name,
age: age,
say: function() {
console.log(this.name + this.age);
}
};
};
const xiaoming = getUserInfo();
// -----------------ES6提供的简洁表示法-------------------
const getUserInfo = (id = 1) => {
// AJAX ....
const name = 'xiaoming';
const age = 10;
return {
name, //会自动去找和它同名的变量,把它的值取过来
age,
say() { //省略function关键字
console.log(this.name + this.age);
}
};
};
const xiaoming = getUserInfo();
属性名表达式
// 属性名表达式
const obj = {
a: 1,
$abc: 2,
'FDASFHGFgfdsgsd$#$%^&*%$#': 3
//没加''之前这种命名方式是不合法的,加了''之后自动转换成普通属性名
//但是这种属性名的属性不能通过.的方式访问,只能通过[]方式访问
};
const key = 'age';
/*
const xiaoming = {
name: 'xiaoming',
[key]: 14 //xiaoming的age属性值为14
};
*/
/*
const xiaoming = {
name: 'xiaoming',
['123'+'abc']: 14 //xiaoming的123abc属性值为14
};
*/
const xiaoming = {
name: 'xiaoming',
[`${key}123`]: 14 //xiaoming的age123属性为14
};
扩展运算符
复制对象
// 复制对象 - 浅拷贝(只是对象的引用)
const obj1 = {
a: 1,
b: 2,
d: {
aa: 1,
bb: 2
}
};
const obj2 = {
c: 3,
a: 9
};
const cObj1 = { ...obj1 }; //利用扩展运算符复制对象
console.log(cObj1.d.aa); //1
cObj1.d.aa = 999;
console.log(cObj1.d.aa); //999
console.log(obj1.d.aa); //999,obj1中的d的aa也被改掉了
//说明这只是浅拷贝,即对对象的引用,而不是生成一个完全独立的副本
合并对象
// 合并对象
const obj1 = {
a: 1,
b: 2,
d: {
aa: 1,
bb: 2
}
};
const obj2 = {
c: 3,
a: 9
};
const newObj = { //将obj1和obj2合并起来赋给newObj,其中a的值是9,因为obj1和obj2都有这个属性,保留靠后的一个
...obj2,
...obj1
};
newObj.d.aa = 22;
console.log(obj1); //obj1中的d的aa被改为22,说明合并也是浅拷贝
部分新方法和属性
Object.is
说明:Object.is与===的区别在对+0和-0以及NaN的判断
// Object.is
// ===
// +0 -0
// NaN
console.log(Object.is(+0, -0)); //false
console.log(+0 === -0); //true
console.log(Object.is(NaN, NaN)); //true
console.log(NaN === NaN); //false
console.log(Object.is(true, false)); //false
console.log(Object.is(true, true)); //true
Object.assign
// Object.assign - 浅拷贝
const obj = Object.assign({a: 1}, {b: 2}, {c: 3}, {d: 4, e: 5});
//执行上述语句后,ojb={a:1,b:2,c:3,d:4,e:5};
const obj = {
a: 1,
b: {
c: 2
}
};
let newObj = Object.assign({a: 3}, obj); //把a和obj对象合并,并对a属性赋值为3
//执行上述语句后,newObj={a:3,b:{c:2}};
console.log(newObj.b.c); //2
newObj.b.c = 100;
console.log(obj.b.c); //修改newObj上的b的为100后,obj上的b的c也被改为100,说明assign也是浅拷贝
Object.keys、Object.values、Object.entries
const obj = {
a: 1,
b: 2,
c: 3,
d: 4
};
Object.keys //返回自身所有属性的属性名所组成的数组
console.log(Object.keys(obj)); //["a","b","c","d"]
// Object.values //返回自身所有属性的属性值所组成的数组
console.log(Object.values(obj)); //[1,2,3,4]
// Object.entries //返回自身所有属性-属性值键值对所组成的数组
console.log(Object.entries(obj)); //[Array(2),Array(2),Array(2),Array(2)]
//Array数组中的内容是[["a",1]["b",2]["c",3]["d",4]]
说明:有了这三个属性后,在使用for-of进行遍历时就不需要像for-in方法一样判断hasOwnProperty
// for - of
for(let key of Object.keys(obj)){ //key是我们自己定义的变量名,对obj的属性名进行遍历
console.log(key);
}
for (let [k, v] of Object.entries(obj)) { //[k,v]是我们自己定义的数组,将obj的entries进行解构赋值
console.log(k, v);
}
__proto__
得到对象的原型
// __proto__
console.log({a: 1}); //a的__proto__是Object
Object.setPrototypeOf
// Object.setPrototypeOf
const obj1 = {
a: 1
};
const obj2 = {
b: 1
}
const obj = Object.create(obj1); //以obj1为原型创建一个对象obj
console.log(obj.__proto__); //obj1,{a:1}
Object.setPrototypeOf(obj, obj2); //第一个参数是需要改变原型的对象,第二个参数是作为原型的对象
console.log(obj.__proto__); //obj的原型从obj1变为obj2,{b:1}
注意:setPrototypeOf()方法性能比较低下,不建议使用
Object.getPrototypeOf
跟访问对象的__proto__属性是一致的
// Object.getPrototypeOf
const obj1 = {a: 1};
const obj = Object.create(obj1);
console.log(obj.__proto__); //obj1,{a:1}
console.log(Object.getPrototypeOf(obj)); //obj1,{a:1}
console.log(obj.__proto__ === Object.getPrototypeOf(obj)); //true
super
用来访问变量的原型对象上的属性和方法
// super 可以访问到原型对象上的属性和方法
const obj = {name: 'xiaoming'};
const cObj = {
say() { //这里必须用简洁表示法,不能是say:function(){},也不能是箭头函数
console.log(`My name is ${super.name}`); //super关键字能访问到cObj原型上的属性name
}
}
Object.setPrototypeOf(cObj, obj); //把cObj的原型指定为obj
cObj.say(); //cObj的原型指定为obj后,就可以使用obj中的name属性
注意:只有当对象方法用简洁表示法定义时才能使用super关键字
数组扩展
和扩展运算符结合
可以把数组展开作为参数传给函数
// 结合扩展运算符使用
function foo(a, b, c) {
console.log(a);
console.log(b);
console.log(c);
}
foo(...[1, 3, 2]);
const user = ['小明', 14, ['吃饭', '打游戏'], '我没有女朋友'];
function say(name, age, hobby, desc) {
console.log(`我叫${ name }, 我今年${ age } 岁, 我喜欢${ hobby.join('和') }, ${ desc }`);
}
say(user[0], user[1], user[2], user[3]); //这是我们一般做法,很麻烦
say(...user); //用扩展运算符展开数组就可以直接解构赋值
function *g() {
console.log(1);
yield 'hi~';
console.log(2);
yield 'imooc';
}
// const arr = [...g()];
// console.log(arr); //["hi~", "imooc"]
const gg = g();
gg.next(); //yield会将函数暂停,调用next后才能继续执行
setTimeout(function () {
gg.next();
}, 1000);
新的方法
apply
apply可以改变调用方法时this的指向
// apply
const user = ['小明', 14, ['吃饭', '打游戏'], '我没有女朋友'];
function say(name, age, hobby, desc) {
console.log(`我叫${ name }, 我今年${ age } 岁, 我喜欢${ hobby.join('和') }, ${ desc }`);
}
say.apply(null, user); //第一个参数是this的指向,不改动,第二个参数是一个数组
Math.max()
// Math.max();
const arr = [1, 2, 233, 3, 4, 5];
console.log(Math.max(...arr)); //作为一个数组传进来,找出数组中的最大值,233
console.log(Math.max.apply(null, arr)); //作为一个数组传进来
//数组长度不确定时,扩展运算符和apply非常好用,但更推荐用扩展运算符
Math.min()
// Math.min();
const arr = [1, 2, 233, 3, 4, 5];
console.log(Math.min(...arr)); //作为一个数组传进来,找出数组中的最小值,1
console.log(Math.min.apply(null, arr)); //作为一个数组传进来
//数组长度不确定时,扩展运算符和apply非常好用,但更推荐用扩展运算符
★利用扩展运算符复制、合并数组★
const arr1 = [1, 2, 3, 4];
const arr2 = [4, 2, 2, 1];
const arr3 = [2.2, '123', false];
const cArr1 = [1, 2, 3, ...arr3];
const cArr2 = [...arr1];
const [...cArr3] = arr3; //把arr3复制给常量cArr3
const cArr4 = [...arr1, ...arr2, ...arr3]; //把arr1、arr2、arr3合并起来赋给cArr4
Array.from
from方法的作用是将一个ArrayLike对象或者Iterable对象转换成一个Array(把类数组转化成数组)
// Array.from
const obj = {
0: 1,
1: '22',
2: false,
length: 2
};
console.log(Array.from(obj, item => item * 2)); //item是类数组中的项,箭头函数将类数组中的项*2
Array.prototype.slice.call(参数是类数组对象); //类数组转数组的方法②
[].slice.call(参数是类数组对象); //类数组转数组的方法③,利用数组的slice.call(),参数是类数组
[...类数组对象] //类数组转数组的方法④,利用扩展运算符
注意:类数组的对象属性名必须为数值型或者字符串型的数字
Array.of
把传入的参数合成数组再去调用方法
// Array.of
console.log(Array.of(1, 2, '123', false)); //输出[1,2,"123",false]
Array#fill
可以通过fill方法填充数组或指定范围
说明:可以用来指定数组参数的默认值
// Array#fill
let arr= new Array(10).fill(0); //长度为10并元素都是0的数组
let arr = new Array(10).fill(0, 0, 3); //长度为10的数组,填充下标为0到3的元素为0,打印[0,0,0,empty*7]
console.log([1, 2, 3].fill(0)); //若数组中本来有值,会把原来的值都覆盖成0,打印[0,0,0]
Array.includes
检测数组中是否包含某项
// Array.includes
var arr = [1, 2, 3, 4];
console.log(arr.includes(1)); //true
console.log(arr.includes(55)); //false
keys
获得数组元素的下标值
// keys
const arr = [1, 2, 3, 444];
console.log(arr.keys()); //是一个迭代器
for (let i of arr.keys()) { //迭代器可以用for-of遍历
console.log(i); //0,1,2,3
}
values
获得数组元素的值
// values
const arr = [1, 2, 3, 444];
for (let v of arr.values()) {
console.log(v); //1,2,3,444
}
entries
获得数组元素的下标-值键值对
// entries
const arr = [1, 2, 3, 444];
for (let [i, v] of arr.entries()) {
console.log(i, v); //[0,1] [1,2] [2,3] [3,444]
}
find
根据条件(回调函数)按顺序遍历数组,当回调返回true时,就返回当前遍历到的值
const res = [1, 7, 6, 3].find(function(value, index, arr){
value % 2 === 0;
});
const res = [1, 7, 6, 3].find((value, index, arr) => value % 2 === 0); //箭头函数表示法
console.log(res);
//value,当前遍历到的值
//index:当前遍历到的值的下标
//arr:调用这个方法的数组
findIndex
根据条件(回调函数)按顺序遍历数组,当回调返回true时,就返回当前遍历到的下标
说明:indexOf无法判断数组中是否有NaN,而findIndex可以通过回调函数来实现
const res = [1, 7, 6, 3, NaN].findIndex((value, index, arr) => Number.isNaN(value));
console.log(res);
扩展运算符在生成器函数中的使用
扩展运算符可以将生成器函数传出来的值作为数组项传给数组
说明:生成器函数通过yield关键字可以暂停函数的执行
function *g() {
console.log(1);
yield 'hi~';
console.log(2);
yield 'imooc~';
}
// const arr = [...g()]; //这样可以一次执行完
const gg = g(); //调用这个函数
gg.next(); //执行下一步,打印1,遇到yield停止
setTimeout(function() {
gg.next(); //过了1s后再执行下一步,打印2,遇到yield停止
}, 1000);
新的数据结构set
let set = new Set([1, 2, 2, 3]); //把数组转化成集合,集合中元素是不能重复的
console.log([...set]); //{1,2,3}
//可以用set去重,然后再转化成数组
ES6 Promise
什么是Promise
Promise对象用于表示一个异步操作的最终状态(完成或失败)以及其返回的值。
同步?异步?
同步任务会阻塞程序执行(alert、for、……)
异步任务不会阻塞程序执行(setTimeout、fs.readFile、……)
可以理解成异步就是等待期间可以去做别的事情,直到收到前一个任务完成的信号再来执行原本要做的事情,因此不会导致程序阻塞。
回调与Promise
回调
function f(cb){
setTimeout(function(){
cb&&cb(); //如果cb存在就调用cb(),cb必须是函数,否则会报错
},1000);
}
f(function(){
console.log(1);
f(function(){
console.log(2);
f(function(){
console.log(3);
...
});
});
});
promise
// promise
// 方法 用于请求数据(模拟)
function f() {
return new Promise(resolve => { //resolve是参数,代表成功时要做的事情
setTimeout(function() {
resolve();
}, 1000);
})
}
//函数f直接return一个Promise实例,Promise的构造函数参数是一个resolve方法
//每个实例都有一个then方法
f()
.then(function() { //.then()表示接下来要做的事情,第一个参数就是一个函数,对应的就是resolve
console.log(1);
return f(); //必须返回f()才能继续访问.then
})
.then(function() {
console.log(2);
return f();
});
信任问题
Promise一旦被确定为成功或失败,就不能再被更改。也就是如果Promise失败了,就不会再调用后面的函数。
传统回调方法将主动权交给第三方库,可能会发生反转控制,而Promise调用的resolve全为自己所书写的流程,很大程度上改善了反转控制的问题
// 信任问题
// 第三方的某个库
function method(cb) {
// 未按所想的预期执行回调
setTimeout(function() {
// 讲道理应该是现在该调用回调了
cb && cb();
// 但是?? 好像这个库有bug啊 emm 被多调用了一次
cb && cb();
}, 1000);
}
// promise一但被确定为成功或者失败 就不能再被更改
function method() {
return new Promise(resolve => {
setTimeout(function() {
// 成功
resolve();
resolve();
}, 1000);
});
}
// 控制反转
function method(cb) {
// 未按所想的预期执行回调
setTimeout(function() {
// 执行回调 但是添油加醋
cb && cb.call({a: 1, b: 2});
}, 1000);
}
function method(cb) {
return new Promise(resolve => {
setTimeout(() => {
resolve(); // 调用的resolve全为自己所写书写的流程 很大程度上改善了反转控制的问题
}, 1000);
});
}
错误处理
说明:Promise判断resolve或reject时,是异步的,所以try-catch对其失效
then()方法加上第二个参数reject
function f(val) {
return new Promise((resolve, reject) => { //resolve成功时的操作,reject失败时的操作
if (val) {
resolve({ name: '小明' }); //只能传递一个参数,传递二个或以上会报错
} else {
reject('404');
}
});
}
//then(resolve, reject)
//then方法中的第二个回调 失败时候做的事
f(false) //这里参数为false,调用的是失败做的事,打印e
.then((data) => { //接收到的是调用resolve或reject函数时给的参数,成功name:'小明',失败'404'
console.log(data)
}, e => {
console.log(e);
})
/*
f(false)
.then(()=>{ //成功时要做的事,对应resolve
console.log('成功');
},()=>{ //失败时要做的事,对应reject
console.log('失败');
})
*/
使用catch()方法可以捕获错误
// catch
// 使用实例的catch方法 可以捕获错误
f(true)
.then(data => {
console.log(data);
return f(false);
})
.then(() => {
console.log('我永远不会被输出');
})
.then(() => { //如果.then里面一直都没有对错误的处理,就会走到.catch中对错误进行处理
})
.catch(e => {
console.log(e);
return f(false) ;
});
//对比
f(true)
.then(data => {
console.log(data);
return f(false);
})
.then(() => {
console.log('我永远不会被输出');
})
.then(() => { //如果.then里面一直都没有对错误的处理,就会走到.catch中对错误进行处理
},e=>console.log('失败')) //这里执行对错误的处理就不会再继续执行.catch中对错误的处理了
.catch(e => {
console.log(e);
return f(false) ; //如果这里返回一个参数为false的f函数,可以继续用.catch捕获它的异常
//但如果这条语句就是最后一条语句了,谁来捕获它的异常呢?目前还没有很好的解决方法
});
使用finally()方法
不论成功还是失败,finally中的内容一定会执行
// finally
// 不论成功还是失败 finally中的内容一定会执行
f(true)
.then(data => {
console.log(data);
return f(false);
})
.catch(e => {
console.log(e);
return f(false); //这里的异常由于没有catch()去捕获,会报错
})
.finally(() => {
console.log(100); //这个100一定会输出
});
Promise的三种状态
pending(进行中)、fulfilled(成功)、rejected(失败)
状态的改变不可逆,一旦决议就不能再修改,并且只能从pending→fulfilled/rejected
Promise的方法
promise.all()
可以把多个promise实例包装成一个新的promise实例
Promise.all([ promise1, promise2 ]) : Promise
说明:
- 如果所有promise实例都决议为成功,那么promise.all()就会决议为成功,并把所有promise实例的resolve回来带的参数组合为一个参数传递给我们,并且顺序是一一对应的。
- 如果有任何一个promise实例决议为失败,那么promise.all()就决议为失败,并且把决议失败的promise错误再传递给我们。
- 当promise.all()的参数为空数组时,就会立即决议为成功
// 模拟需要多个请求的数据 才能进行下一步操作的情况
function getData1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('第一条数据加载成功');
resolve('data1');
}, 1000);
});
}
function getData2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('第二条数据加载成功');
resolve('data2');
}, 1000);
});
}
function getData3() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('第三条数据加载成功');
resolve('data3');
}, 1000);
});
}
function getData4() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// console.log('第四条数据加载成功');
reject('data4 err'); //这里设置为出错
}, 500);
});
}
let p = Promise.all([getData1(),getData2(),getData3(),getData4()]);
p.then(() => {
console.log(arr); //第一个参数,成功时的操作,打印["data1","data2","data3","data4"],都是resolve()传递回来的值
}, e => { //第二个参数,失败时的操作,打印e
console.log(e);
});
//空数组
let p = Promise.all([]); //直接决议为成功
p.then(() => {
console.log('dfsafd');
}, e => {
console.log(e);
});
//对比,不用promise.all()
let count = 0;
let err = false;
function func() {
if (count < 4) return;
if (err) {
// ....
}
console.log('全部拿到了 !');
}
function getData1() {
setTimeout(() => {
console.log('第一条数据加载成功');
count ++;
func();
}, 1000);
}
function getData2() {
setTimeout(() => {
console.log('第二条数据加载成功');
count ++;
func();
}, 1000);
}
function getData3() {
setTimeout(() => {
console.log('第三条数据加载成功');
count ++;
func();
}, 1000);
}
function getData4() {
setTimeout(() => {
console.log('第四条数据加载成功');
count ++;
func();
}, 1000);
}
getData1();
getData2();
getData3();
getData4();
promise.race()
可以把多个promise实例包装成一个新的promise实例
Promise.all([ promise1, promise2 ]) : Promise
说明:
- 如果有任何一个promise实例决议为成功,那么promise.race()就会决议为成功,并把所有promise实例的resolve回来带的参数组合为一个参数传递给我们,并且顺序是一一对应的。
- 如果有任何一个promise实例决议为失败,那么promise.race()就决议为失败,并且把决议失败的promise错误再传递给我们。
- 当promise.race()的参数为空数组时,就会永远被挂起
function getData1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('第一条数据加载成功');
reject('err'); //这里设置为出错
}, 500);
});
}
function getData2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('第二条数据加载成功');
resolve('data2');
}, 1000);
});
}
function getData3() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('第三条数据加载成功');
resolve('data3');
}, 1000);
});
}
let p = Promise.all([getData1(),getData2(),getData3()]);
p.then(data => {
console.log(data);
//第一个参数,成功时的操作
//打印["第一条数据加载成功",err,"第二条数据加载成功","第三条数据加载成功"]
//都是resolve()传递回来的值
}, e => { //第二个参数,失败时的操作,打印e
console.log(e);
});
//空数组
let p = Promise.all([]); //传入空数组直接被挂起
p.then(() => {
console.log('dfsafd');
}, e => {
console.log(e);
});
// 不使用pormise.race
let flag = false;
function func(data) {
if (flag) return;
flag = true;
console.log(data);
}
function getData1() {
setTimeout(() => {
console.log('第一条数据加载成功');
func({name: 'xiaoming'});
}, 500);
}
function getData2() {
setTimeout(() => {
console.log('第二条数据加载成功');
func({name: 'xiaohong'});
}, 600);
}
promise.resolve()
Promise.resolve()和Promise.reject()常用来生成已经被决议为失败或成功的promise实例
Promise.resolve()接收参数有三种情况:
传递一个普通的值
传递一个普通的值,会立即决议为成功,并填充这个值
// 传递一个普通的值,会立即决议为成功,并填充这个值
let p1 = new Promise(resolve => {
resolve('成功!');
});
let p2 = Promise.resolve('成功!'); //传递一个字符串
//以上两种方式是等价的,没有任何区别
传递一个promise实例
传递一个promise实例,则返回这个promise实例
// 传递一个promise实例,则返回这个promise实例
let poruomiesi = new Promise(resolve => {
resolve('耶!')
});
// 直接返回传递进去的promise
let p = Promise.resolve(poruomiesi); //把poruomiesi实例返回给了p
p.then(data => void console.log(data));
console.log(p === poruomiesi); //true
传递一个thenable
如果传递的是个thenable,则会把它包装成promise对象并立即执行该对象的then方法
// 传递一个thenable
// 如果传递的是个thenable,则会把它包装成promise对象并立即执行该对象的then方法
// 鸭子类型,看上去像鸭子就具有鸭子的属性和方法
let obj = {
then(cb) { //obj对象的then方法
console.log('我被执行了');
cb('哼!');
},
oth() {
console.log('我被抛弃了');
}
}
// 立即执行then方法
Promise.resolve(obj).then(data => { //调用obj对象的then方法,参数是一个函数,功能是打印data
console.log(data); //打印我被执行了哼!
});
promise.reject()
reject会产生一个决议失败的promise并直接传递值
//reject会产生一个决议失败的promise并直接传递值
Promise.reject({ then() { console.log(1) } })
.then(() => { //第一个参数,成功时的操作
console.log('我不会被执行');
}, e => { //第二个参数,失败时的操作
console.log(e);
});
注意:
- Promise在决议之后是异步执行接下来要做的事的
- 在当前这一轮事件循环中,异步任务肯定在同步任务之后执行
console.log(1);
let p = new Promise(resolve => {
console.log(2);
resolve(); //这里调用resolve()就是调用p.then()中的函数,它是异步执行的
console.log(3);
});
console.log(4);
p.then(() => {
console.log(5);
});
console.log(6);
//打印结果 1 2 3 4 6 5
//说明resolve()是异步执行的,它把1 2 3 4 6都打印完了才执行
把同步任务转成异步任务
// 把同步的任务转成异步任务
function createAsyncTask(syncTask) {
return Promise.resolve(syncTask).then(syncTask => syncTask());
}
createAsyncTask(() => {
console.log('我变成了异步任务!!!');
return 1 + 1;
}).then(res => { //在,then()中可以拿到return的值,即2
console.log(res); //2
});
console.log('我是同步任务!');
//打印我是同步任务!我变成了异步任务!2
小案例——多张图片加载完才显示
// 页面中有个板块 需要多张图片加载完之后才能进行展示
const loadImg = src => { //参数src是图片的链接,loadImg是一个函数
return new Promise((resolve, reject) => { //返回一个Promise实例对象,参数是一个函数,其中resolve是成功时的操作,reject是失败时的操作
const img = new Image(); //创建一个图片对象
img.src = src; //将传进函数的src赋给这个图片对象的src属性
img.onload = () =>{ //图片加载完成时调用resolve()方法
resolve(img);
};
img.onerror = () =>{ //图片加载失败返回一个字符串
reject(e);
};
});
};
//不能写成img.οnlοad=resolve(img)和img.οnerrοr=reject('加载失败')
//因为resolve和reject是函数,后面跟括号就是调用了它,会在调用完之后才赋值给等号左边的变量,这不是我们要的
const imgs = [
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1526734981&di=fe12efe9e3a76bd3bb5ac202a3c76823&imgtype=jpg&er=1&src=http%3A%2F%2Fd15.lxyes.com%2F15xm%2Fact%2F20151105%2F20%2F99112408.jpg',
'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1252816855,3131381110&fm=27&gp=0.jpg',
'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1906477750,651116720&fm=27&gp=0.jpg'
];
Promise.all(imgs.map(src => loadImg(src))).then((arr) => {
//遍历imgs,将imgs中的src传入loadImg,调用函数,返回多个promise实例,再调用.then得到一个数组[img,img,img]
console.log(arr);
arr.forEach((img) => {
document.body.appendChild(img);
});
}).catch(e)=>{
console.log(e);
});
ES6 class
类与对象的核心就是封装
使用类与对象思想开发的好处
- 降低维护成本
- 使代码高度复用
- 扩充方便灵活
- 降低设计成本
- 使用简单
- ……
ES6中类的特性
多态:同一个接口,不同的表现,让子类有各自不同的属性和方法
继承:子类拥有父类的属性和方法
封装
//类的声明
class Car{
constructor(){ //constructor是构造函数
alert('开始造车');
}
}
//实例化一个对象
const car=new Car(); //Car()调用一个构造函数
//含参的构造函数
class Car{
constructor(wheel,color,length,width){
this.wheel=wheel;
this.color=color;
this.length=length;
this.width=width;
this.speed=0;
}
//方法
speedUp(){
this.speed+=1;
}
}
const car=new Car(3,'#fff',20,40);
car.color='#000';
car.speed();
console.log(car.speed); //2
class Car{
constructor(...arg){
console.log(arg); //[3,"蓝色"]
}
}
new Car(3,'蓝色');
注意:用同样的构造函数实例化的对象之间是独立的,没有任何关联
静态属性与静态方法
静态属性和静态方法不会被类的实例所拥有,只是类自身拥有,只能通过类调用。
声明静态方法要用static关键字,声明静态属性用类名.属性名=属性值
静态方法与普通方法是可以重名的
class Car{
constructor(){
this.speed=0;
Car.totalCar+=1; //类对象共用属性,用来做记录
}
speedUp(){
this.speed+=1;
}
static repair(car){
console.log('我是修车的');
console.log('我现在要修的车是:',car);
if(!car.speed){
speed=0;
}
}
checker(){ //普通函数
console.log('开始自检');
if(this.error===0){
console.log('检测完毕 一切正常');
}
}
static checker(){ //静态函数
console.log('我是质检员,我要开始检查了');
}
}
Car.totalCar=0; //静态属性可以直接定义在类外,用类名访问静态属性
Car.config={ //可以给车类设置默认属性
wheel:4;
color:'#000';
}
new Car();
new Car();
console.log(Car.totalCar); //2
Car.repair('1号车'); //静态方法只能通过类访问
//假设一辆车只知道color属性,要去维修
Car.repair({
color:'#100'
});
car.checker(); //调用的是普通方法
Car.checker(); //调用的是静态方法
getter与setter
getter / setter方法类似于给属性提供钩子,在获取属性值和设置属性值的时候做一些额外的事情
ES5中的getter和setter方法的实现有两种方式:
- 在对象字面量中书写get / set方法
- 通过Object.defineProperty为属性添加get / set方法
注意:如果get/set方法与属性同名会陷入不断调用方法的死循环
ES5中的getter和setter方法
//在对象字面量中书写get / set方法
const obj = {
name: '',
get name() {
console.log('123');
return this.name;
},
set name(val) {
this.name = val;
}
}
obj.name=3 ;
//如果get/set方法与属性同名会陷入不断调用方法的死循环,因此要把属性名改掉
const obj = {
_name: '',
get name() {
console.log('123');
return this._name;
},
set name(val) {
this._name = val;
}
}
obj.name=222;
console.lof(obj); //{_name:222}
console.log(obj.name); //222
//访问obj.name时被get方法拦截,返回_name的值
//Object.defineProperty
var obj = {
_name: ''
};
Object.defineProperty(obj, 'name', {
get: function() {
console.log('正在访问name');
return this._name; //this指向当前 obj对象
},
set: function(val) {
console.log('正在修改name');
this._name = val;
}
});
obj.name = 10;
console.log(obj.name);
===========================================================
Object.defineProperty(obj, 'age', {
value: 19,
enumerable: true //不写这句age属性是无法被遍历到的
});
var i;
for (i in obj) {
console.log(i);
}
console.log(obj);
ES6中getter和setter方法
class Person {
constructor() {
this._name = '';
}
get name() {
console.log('正在访问name');
return `我的名字是${ this._name }`;
}
set name(val) {
console.log('正在修改name');
this._name = val;
}
}
const person = new Person();
person.name = '鸽王';
console.log(person.name);
类表达式
类表达式与函数表达式形式是类似的,可以用一个变量接收一个类
const Person = class{
constructor{
console.log('我是歌手');
}
}
//类也可以有名字
const Person = class P{
constructor{
P.a=1; //若要添加静态属性或者静态方法最好用给类起的类名,不需要一直修改
console.log('我是歌手');
console.log(P); //类内部能够访问到P
}
}
//但是这个类名只能被类内部访问,出了这个范围就访问不到了
console.log(P); //在类外访问,报错,提示未定义
const Person1 = new class P{
constructor(){
P.a=1;
console.log('我是歌手');
}
}(); //这样就能自动执行这个构造函数
name属性与new.target属性
//new
class Person {
}
const Humen = class P{
}
console.log(Person.name); //Person,没起名的name就是类名
console.log(Humen.name); //返回P,起了名,用类.name访问就是对象自己的名字
//new.target
class Car {
constructor() {
console.log(new.target); //指向new关键字后面的类也就是Car
}
}
new Car();
说明:ES6中的类是ES5中的语法糖(另一种写法,本质一样),相当于是ES5中模拟类的一种写法
// 语法糖
function Car() {
if (new.target !== Car) {
throw Error('必须使用new关键字调用Car');
}
if (!(this instanceof Car)) {
throw Error('必须使用new关键字调用Car');
}
}
new Car();
class Car {
constructor() {
console.log(new.target);
}
}
Car(); //不使用new关键字调用会报错
在ES5中模拟类
ES5中没有类,但是有构造函数,可以用构造函数来模拟一个类。同样用new 对象名()来调用构造函数
// 构造函数
//ES6中类的定义和构造函数
class Car {
constructor() {
}
}
//ES5中的构造函数
//无参
function Car() {
}
new Car();
//含参
function Person(name, age){
this.name = name;
this.age = age;
}
console.log(new Person('张三', 11));
//使用new关键字调用一个函数,这个函数就会被作为构造函数去调用,调用构造函数的返回值会返回一个对象
当用new关键字调用函数时发生了什么? 为什么会获得一个新对象?
- 创建一个空的对象
- 把构造函数的prototype属性作为空对象的原型,这样空对象就可以访问构造函数的prototype里的方法
- this赋值为这个空对象
- 执行函数
- 如果函数没有返回值则返回this(返回之前那个空对象)
如果没有构造函数,我们如何根据构造函数的原理模拟一个构造函数?
function Constructor(fn, args) { //fn表示作为构造函数的函数,args表示参数
var _this = Object.create(fn.prototype); //创建出来的空对象以fn.prototype为原型,可以访问fn.prototype上的函数
var res = fn.apply(_this, args); //fn的apply函数中的this指向当前对象,参数就是args
return res ? res : _this; //有返回值就返回,没有就返回当前对象
}
//构造函数设置属性
function Person(name, age) {
this.name = name;
this.age = age;
}
//原型设置方法
Person.prototype.say = function() {
console.log('我叫' + this.name);
}
var person = Constructor(Person, ['张三', 12]);
console.log(person);
类的继承
继承可以让子类获得父类的方法,属性,可以扩充、增加新的方法、属性等。继承的关键字是extends
// extends
//父类定义
class Human {
constructor(name, age, sex, hobby) {
this.name = name;
this.age = age;
this.sex = sex;
this.hobby = hobby;
}
desc() {
const { name, age, sex, hobby } = this; //解构赋值
console.log(`我叫${ name }, 性别${ sex }, 爱好${ hobby }, 今年${ age }岁`);
}
eat() {
console.log('吧唧吧唧');
}
}
//子类定义
class FEEngineer extends Human {
constructor(name, age, sex, hobby, skill, salary) { //一般需要传父类的属性参数,若不传则相应属性值为undefined
super(name, age, sex, hobby); //super();即调用父类的构造函数,获得父类的属性和方法,且必须写在子类属性声明前
this.skill = skill;
this.salary = salary;
}
say() {
console.log(this.skill.join(','));
}
}
const feer = new FEEngineer(
'张四',
12,
'女',
'洗澡',
['es6', 'vue', 'react', 'webgl'],
'1k'
);
feer.eat(); //eat是从父类继承来的方法,可以直接调用
super关键字的作用
- 作为父类构造函数调用(作为函数调用)
- 作为对象调用
说明:
作为父类构造函数调用时,相当于把子类的this丢到父类构造函数中,也就是说在父类构造函数中操作的其实是子类的this,是对子类的属性进行初始化。
作为对象调用又分为非静态方法中访问super->父类原型以及静态方法中访问super->父类
在调用super时,父类的this始终是子类的this
// extends
//父类定义
class Human {
constructor(name, age, sex, hobby) {
this.name = name;
this.age = age;
this.sex = sex;
this.hobby = hobby;
}
desc() {
const { name, age, sex, hobby } = this; //解构赋值
console.log(`我叫${ name }, 性别${ sex }, 爱好${ hobby }, 今年${ age }岁`);
}
eat() {
console.log('吧唧吧唧');
}
}
//静态属性
Human.total=988888;
//子类定义
class FEEngineer extends Human {
constructor(name, age, sex, hobby, skill, salary) { //一般需要传父类的属性参数,若不传则相应属性值为undefined
super(name, age, sex, hobby); //super();即调用父类的构造函数,获得父类的属性和方法,且必须写在子类属性声明前
this.skill = skill;
this.salary = salary;
}
say() {
//console.log(super); //直接这样调用会报错
//console.log(super.eat); //非静态方法中访问suepr代表的是父类原型,返回eat方法,也可以用eat()去调用它
console.log(this.skill.join(','));
}
static test(){
console.log(super.name); //静态方法中super指向的是父类,返回Human
console.log(super.total); //访问到Human的静态属性,说明super指向父类
}
}
const feer = new FEEngineer(
'张四',
12,
'女',
'洗澡',
['es6', 'vue', 'react', 'webgl'],
'1k'
);
feer.eat(); //eat是从父类继承来的方法,可以直接调用
多态
多态指同一个接口在不同的情况下做不同的事,即相同接口不同表现
多态的优点:
- 扩充性与灵活性
- 暴露接口
接口本身只是一组定义,实现都是在类里面。可以理解成需要子类去实现的方法。
// 多态
class Human {
say() {
console.log('我是人');
}
}
class Man extends Human {
say() { //子类可以重写父类的方法
super.say(); //调用父类同名方法,这里super指向父类原型,输出我是人
console.log('我是小哥哥');
}
}
class Woman extends Human {
say() { //子类可以重写父类的方法
super.say(); //调用父类同名方法, 输出我是人
console.log('我是小姐姐');
}
}
new Man().say(); //不同子类对父类的方法有自己具体的实现,输出我是小哥哥
new Woman().say(); //输出我是小姐姐
说明:子类调用父类同名方法时,若子类构造函数中重写了该同名方法,就调用子类的方法,若子类没有实现这个方法,就会调用父类的方法。
重载
根据函数类型和参数个数去做不同的事
// 重载
class SimpleCalc {
addCalc(...args) { //参数个数不确定,使用省略参数
if (args.length === 0) {
return this.zero();
}
if (args.length === 1) {
return this.onlyOneArgument(args);
}
return this.add(args);
}
zero() {
return 0;
}
onlyOneArgument() {
return args[0];
}
add(args) {
return args.reduce((a, b) => a + b, 0); //reduce()用来对数组求和
}
}
function post(url, header, params) {
if (!params) {
params = header;
header = null; // undefined
}
}
post('https://imooc.com', {
a: 1,
b: 2
});
ES5中的继承
这里所介绍的继承不是真正意义上面向对象的继承,而是利用原型来模拟实现的
利用构造函数
// 利用构造函数
function P() {
this.name = 'parent';
this.gender = 2;
this.say = function() { //父类方法可以在构造函数中定义
console.log('好的好的!我一定到!!咕咕咕');
}
}
P.prototype.test = function() { //父类原型定义的方法
console.log('我是一个test方法');
}
function C() {
P.call(this); //在子类中调用一遍父类构造函数,把this作为call的参数就可以把父类构造函数中的this改为子类的this
this.name = 'child'; //子类自己的属性
this.age = 11;
}
var child = new C(); //实例化一个子类对象
child.say(); //调用从父类继承来的方法
child.test(); //不能继承父类原型上的方法
//解决方法
C.prototype = new P(); //这样C的实例就可以访问到P的原型中的方法了
注意:ES5中模拟继承是不能继承父类原型上定义的方法的
解决方法:把父类作为子类的prototype
Babel
Babel是一个JavaScript的编译器,作用是把大部分浏览器不支持的代码编译成浏览器支持的代码
https://www.babeljs.cn/ babel中文网
安装步骤:
- 安装node.js http://nodejs.cn/
- 打开命令行窗口,输入node -v和npm -v检查是否安装成功
- 在桌面新建一个文件夹,假设叫做project,在命令行中通过 cd desktop cd project 进入桌面的文件夹,也可以通过cd.. 退回上一级目录,或者通过 cd 绝对路径 访问到project
- npm是node的包管理工具,就像我们写的页面依赖jquery、bootstrap,node也需要许多依赖,这些依赖就管理在npm上,相当于是一个很大的应用商店,我们在shell中用npm install -xxxx就可以到这个应用商店中下载我们需要的依赖
- 按住shift+右键,用powershell打开project,键入 npm init 进行初始化,检查完所有信息之后回车,project中出现package.json,也可以键入 npm init -y 来直接创建json文件,无需确认信息。
- 到babel官网中,点击文档→安装,CLI工具,复制命令,粘贴到命令行,运行。打开package.json会发现json中增加了依赖。
- 在package.json的scripts中,添加"buld":"babel entry.js"
{
"name": "project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build":"babel entry.js" //手动添加的
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.8.4", //在命令行中安装cli、core后自动生成的
"@babel/core": "^7.8.6"
}
}
- 我们安装的包都会放在node_modules下
- 创建一个js文件,假设叫做entry。在powershell中键入:new-item fileName.fileType
- 在entry.js中写一些代码,在powershell中键入:npm run build,执行babel entry.js
- 此时可以看到命令行窗口中js代码没有任何变化,接下来安装转换规则依赖包,在powershell中键入:npm i -D @babel/preset-env(老师的课件是18年的,安装的命令是npm i -D babel-preset-env,但是最后编译的时候出错了,故改成新版的写法)
- 安装完成后可以看到json文件的devDependencies中多了"@babel/preset-env"(旧版本是多了"babel-preset-env")
- 下面配置一个文件,告诉我们的babel根据什么规则去编译,在powershell中键入:new-item .babelrc
- 在新建的.babelrc文件中键入并保存
{
"presets":[
"@babel/preset-env"
]
}
/*旧版本写法
{
"presets":[
"env"
]
}
*/
- 再次在powershell中键入:npm run build,进行编译,成功。
- 如果要再加上更复杂的配置,比如想要我们的代码能够兼容最新版本的浏览器
{
"presets":[
[
"@babel/preset-env",
{
"targets":{
"browsers":["last 1 version"] //可以兼容最新版本浏览器
}
}
]
]
}
- 在json文件中的build后面加上-o output.js(output是自己起的),再运行,就可以把转换结果输出到文件中
{
"name": "project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "babel entry.js -o output.js"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.8.6",
"@babel/preset-env": "^7.8.6",
"babel-preset-env": "^1.7.0"
}
}
"build": "babel entry.js -o output.js -w"
//加上-w可以自动监听文件是否改变,如果改变就会自动编译
在entry.js中键入以下代码,会报错,因为静态变量定义在类内以及属性定义在构造函数外是不允许的
class Person{
static aa=1;
bb=2;
static A(){
alert('b');
}
constructor(){
}
}
在powershell中键入:npm install --save-dev babel-plugin-transform-class-properties
package.json中自动增加:"babel-plugin-transform-class-properties": "^6.24.1",
修改.babelrc,添加"plugins":["transform-class-properties"],再运行就不再报错了
{
"presets":[
[
"@babel/preset-env",
{
"targets":{
"browsers":["last 1 version"]
}
}
]
],
"plugins":["transform-class-properties"] //手动添加
}
下面我们把entry.js代码进行修改,写一个车类,运行不再报错,这样就能更加语义化,符合我们的书写习惯
class Car {
static total_car = 0; //使用babel后就可以把静态属性放到类内了
color = '#000'; //属性也可以写在构造函数外面,看起来更直观
constructor(color) {
Car.total_car += 1;
this.color = color;
}
}
new Car();
new Car();
new Car();
console.log(Car.total_car);