字符串扩展
- 模板字符串
模板字符串可以很方便的代替ES5字符串拼接,格式如下:
`字符串${变量}字符串`
代码示例
let a = {
name: 'Adam',
age: 10,
sex: 'male'
}
let b = `
Hello
${a.name}
${a.age}
`;
console.log(b);
/*
Hello
Adam
10
*/
- API
//判断字符串中是否包含某个字符
let str = 'ddfferrgg';
str.includes('d'); //true
//判断字符串是否以某些字母开始或结束
let str = 'string';
str.startsWith('str'); //true
str.endsWith('ng'); //true
//重复字符串
let str = 'abc';
str.repeat(2); // abcabc
//字符串补白(ES7草案)
console.log('4'.padStart(3, '1')); 114
console.log('4'.padEnd(5, '0')); 40000
//输出字符串模板原始内容, 不转意
console.log(String.raw`Hi\n${1+2}`) //Hi\n${1+2}
函数扩展
- 函数默认值
//ES5
function show(a, b) {
var a = a || 8;
return a + b;
}
//ES6
function show(a=6, b) {
return a + b;
}
- 箭头函数
//箭头函数写法
let func = (a=6, b) => {
console.log(a + b);
return a + b;
}
console.log(func(undefined, 2)); //8
- 箭头函数相当于匿名函数, 由于箭头函数的无法绑定this,所以它的this会绑定到上层函数
对象的扩展
- 对象的简写
let a = 5;
let obj = {
a, //相当于 a: a
b() {
console.log(this.a);
} //相当于 b: function() {console.log(a)}
}
Object.keys(obj) -获得对象所有的key名
Object.assign(obj1, obj2,[...obj]) -后面所有的对象合并至obj1中
Object.defineProperty(obj, key, {})
Object.defineProperty(obj, key, {
value: 5, //属性的值
writable: false, //如果为false, 属性值就不能被重写, 只能为只读
configurable: false, //一旦为false, 就不能设置其他属性(value, writable, enumerable)
enumerable: false, //是否能在for...in循环中遍历或Object.keys中列举出来
get: function() {}, //get访问器
set: function() {} //set访问器
})
- class
//创建类
class Person {
//构造函数
constructor(name, sex) {
this.name = name;
this.sex = sex;
}
//静态方法 在一个方法前加上static关键字, 该方法就不会被实例继承, 直接通过类调用
static getMethod() {
return 'hello';
}
//对象的方法
showName() {
console.log(this.name);
}
}
//类的继承
class Man extends Person {
constructor(name, sex, age) {
super(name, sex); //name, sex属性继承
this.age = age;
}
}
数组的扩展
- Array.of()
Array.of()方法是为了解决Array构造函数传入一个数值,数组length属性被设定为该值的的问题
//Array构造函数
let items = new Array(2);
console.log(items.length); //2
console.log(items[0]); //undefined
console.log(items[1]); //undefined
//Array.of
let items = Array.of(2);
console.log(items.length); //1
console.log(items[0]); //2
- Array.from()
Array.from()方法可以接受可迭代对象或者类数组对象作为第一个参数, 最终返回一个数组,第二个参数为一个映射函数,用来将类数组对象中的每一个值转换成其他形式, 最后将结果存储在结果数组的相应索引中
function translate() {
return Array.from(arguments, value => value + 1);
}
let numbers = translate(1, 2, 3);
console.log(numbers); //[2,3,4]
Array.from()的第三个参数表示映射函数的this值
let helper = {
diff: 1,
add(value) {
return value + this.diff
}
};
function translate() {
return Array.from(arguments, helper.add, helper)
}
let numbers = translate(1, 2, 3);
console.log(numbers);
- find()和findIndex()方法
find()和findIndex()方法都接受两个参数: 一个是回调函数; 另一个是可选参数, 用于指定回调函数中this的值。执行回调函数时, 传入的参数分别为: 数组中的
某个元素和该元素在数组中的索引以及数组本身, 与传入map()和forEach()方法的参数相同
let numbers = [25, 30, 35, 40, 45];
console.log(numbers.find(n => n > 33)); //35
console.log(numbers.findIndex(n => n > 33)); //2
- fill()
fill()方法可以用指定的值填充一至多个数组元素, 当传入一个值时, fill()方法会用这个值重写数组中的所有值, 第二个参数表示从哪个索引开始, 第三个参数表
示从哪儿索引结束(不包括该索引), 若不传第三个值, 则从索引开始填充至数组末尾
// 一个参数
let numbers = [1, 2, 3, 4];
numbers.fill(1);
console.log(numbers); //[1, 1, 1, 1]
//两个参数
let numbers = [1, 2, 3, 4];
numbers.fill(1, 2);
console.log(numbers); //[1, 2, 1, 1]
//三个参数
let numbers = [1, 2, 3, 4];
numbers.fill(0, 1, 3);
console.log(numbers); //[1, 0, 0, 4]
- copyWithin()
copyWithin()方法与fill()方法相似, 其也可以改变数组中的多个元素。fill()方法是将数组元素赋值为一个指定的值, 而copyWithin()方法则是从数组中复制元素的值, 调用copyWithin()方法时,需要传入两个参数: 一个是该方法的开始填充值的索引位置, 另一个是开始复制值得索引位置, 第三个参是可选参数, 用于指定停止复制的索引位置(不包括该索引)
let numbers = [1, 2, 3, 4];
numbers.copyWithin(2, 0);
console.log(numbers); //[1, 2, 1, 2]
- 定型数组
待填充
解构
- 数组解构
数组解构一般有以下几种形式
let [a,b,c] = [1,2,3] //1, 2, 3
let [a,b,c] = [1,2] //1, 2, undefined
let [a,b,c=2] = [1,2] //1, 2, 2
let [a,,,b] = [1,2,3,4] // 1, 4
let [a,b,...rest] = [1,2,3,4,5,6] //1,2,[3,4,5,6]
数组解构赋值的使用场景
//变量交换
let a = 1
let b = 2
[a,b] = [b,a]
console.log(a,b) //2, 1
//接收函数返回值
function f() {
return [1, 2]
}
let [a,b] = f() //1,2
- 对象结构赋值
let o = {
p: 23,
q: 'Adam'
}
let {p, q} = o //23 Adam
let o = {
p:23
}
let {p=20, q=45} = o // 23 45
let o = {
p: 23,
q: 'Adam'
}
let {p:a, q} = o
console.log(a, q) //23 Adam
对象解构赋值应用
对象的嵌套
let metaData = {
title: 'abc',
test: [{
title: 'test',
desc: 'description'
}]
}
let {title:esTitle, test:[{title:cnTitle}]} = metaData
console.log(esTitle, cnTitle) //abc test
解构表达式改变值
let node = {
type: 'Identifier',
name: 'foo'
},
type = 'Literal',
name = 5
// 使用解构来分配不同的值
({type, name} = node)
console.log(type) //Identifier
console.log(name) //foo
对象解构表达式给函数传参
let node = {
type: 'Identifier',
name: 'foo'
},
type = 'Literal',
name = 5
function outputInfo(value) {
console.log(value === node) //true
}
outputInfo({type, name} = node)
console.log(type) //Identifier
console.log(name) //foo
Set-Map
set
set集合内不能有重复的值
- set集合声明
//方法1
let list = new Set();
list.add(1);
list.add(2);
list.add(3);
//方法2
let list = new Set([1,2,3]);
API
add, delete, clear, hasset的遍历
let list = new Set(['a', 'b', 'c'])
//set的key和value一样
for(let key of list.keys()) {
console.log(key) //a b c
}
for(let value of list.values()) {
console.log(value) //a b c
}
for(let value of list) {
console.log(value) //a b c
}
for(let [key, value] of list.entries()) {
console.log(key, value) //a a b b c c
}
list.forEach(function(item) {
console.log(item) //a b c
})
- WeakSet
WeakSet跟Set的区别就是数值类型不同, 只能是对象, 而且对象是地址引用, 并且不检测垃圾回收机制, WeakSet不能遍历
let weakList = new WeakSet()
map
map集合是一种类似于对象的数据结构, 也是以键值对形式出现的, map的键支持多种数据类型
- map声明
//第一种
let map = new Map()
let arr=['123']
map.set(arr, 456)
console.log(map.get(arr)) //456
//第二种
let map = new Map([['a', 123], ['b', 456], ['c', 789]])
console.log(map) //a=>123 b=>456 c=>789
API
跟set差不多, 遍历也一样WeakMap
跟set与WeakSet区别一样map与array对比
//数据的增删查改
let map = new Map()
let array = []
//增
map.set('t', 1)
array.push({t:1})
//查
let map_exist = map.has('t') //true
let array_exist = array.find(item => item.t) //Object{t:1}
//改
map.set('t', 2)
array,forEach(item => item.t?item.t=2:'')
//删
map.delete('t')
let index=array.findIndex(item => item.t)
array.splice(index, 1)
- set和array对比
let set = new Set()
let array = []
//增
set.add({t:1})
array.push({t:1})
//查
let set_exist = set.has({t:1})
let array_exsist = array.find(item => item.t)
//改
set.forEach(item => item.t?item.t = 2 : '')
array,forEach(item => item.t?item.t=2:'')
//删
set.forEach(item => item.t?set.delete(item):'')
let index=array.findIndex(item => item.t)
array.splice(index, 1)
- map, set与object对比
let item = {t:1}
let map = new Map()
let set = new Set()
let obj = {}
//增
map.set('t', 1)
set.add(item)
obj['t'] = 1
//查
let map_exist = map.has('t')
let set_exist = set.has(item)
let obj_exist = 't' in obj
//改
map.set('t', 2)
item.t = 2
obj['t'] = 2
//删
map.delete('t')
set.delete(item)
delete obj['t']
promise与异步编程
- 异步编程的背景知识
JavaScript引擎是基于单线程(Single-threaded)事件循环的概念构建的, 同一时刻只允许一个代码块在执行, 与之相反的是像Java和C++一样的语言, 它们允许多个不同的代码块同时执行。对于基于线程的软件而言, 当多个代码块同时访问并改变状态时, 程序很难维护并保证状态不会出错。
JavaScript引擎同一时刻只能执行一个代码块, 所以需要跟踪即将运行的代码, 那些代码被放在一个任务队列(job queue)中, 每当一段代码准备执行时, 都会被添加到任务队列。每当JavaScript引擎中的一段代码结束执行, 事件循环(event loop)会执行队列中的下一个任务, 它是JavaScript引擎中的一段程序, 负责监控代码执行并管理任务队列。队列中的任务会从第一个按顺序执行到最后一个。
JavaScript作为一门为web而生的语言, 他一开始就需要能够响应异步的用户交互, 如点击等操作。所以异步编程一直作为JavaScript的一个强大功能而存在,再加上Nodejs用回调函数代替了事件, 使异步编程在JavaScript领域变得更加流行, 但是随着更多程序开始使用异步编程后, 事件和回调函数已经无法满足开发者的需求, 因此ES6中就加入了Promise用来处理更复杂的需求。
- 事件模型
用户点击按钮或按下键盘上的按键会触发类似onclick这样的事件, 它会向任务队列添加一个新任务来响应用户的操作, 这是JavaScript种最基础的异步编程形式, 直到事件触发时才执行事件处理程序, 且执行上下文与定义时的相同。
let button = document.querySelector('.btn');
button.addEventListener('click', function(event) {
console.log('clicked');
})
这段代码中, 点击button后会执行console.log('clicked'), 监听器内的函数被添加到任务队列中, 只有当前面的任务都完成后它才会被执行。
事件模型适用于处理简单的交互, 然后将多个独立的异步调用连接在一起会是程序更加复杂, 你必须跟踪每个事件的事件目标(如代码中的button)。此外, 必须要保证事件在添加事件处理程序之后才会被触发。尽管事件模型适用于响应用户交互和完成类似的低频功能, 但是对于更复杂的需求来说, 它并不是很灵活。
- 回调模式
Nodejs通过普及回调函数来改进异步编程模型, 回调模式与事件模型类似, 异步代码会在未来的某个时间点执行, 二者的区别是回调模式中被调用的函数时作为参数传入的
readFile('example.txt', function(err, contents) {
if (err) {
throw err;
}
console.log(contents);
})
console.log('Hi!');
上面nodejs代码就是典型的回调模式。readFile()函数读取磁盘上的某个文件(example.txt), 读取结束后执行回调函数(function(err, contents){})。如果出现错误, 错误对象会被赋值给回调函数的err参数; 如果一切正常, 文件内容会以字符串的形式被赋值给contents参数。
由于使用了回调模式, readFile()函数在读取文件时, 暂时不会执行回调函数。也就是说, 调用readFile()函数后, 会先执行后面的console.log('Hi!'), 当readFile()结束执行时, 会向任务队列末尾添加一个新任务, 该任务包含回调函数, 当队列前面所有的任务完成后才执行该任务。
回调模式比事件模型更灵活, 可以通过回调函数链接多个回调
readFile('example.txt', function(err, contents) {
if (err) {
throw err;
}
writeFile('example.txt', function() {
if (err) {
throw err;
}
console.log('File was written!');
});
});
上面的例子就是一个典型的回调链接, 这段代码在成功调用readFile()函数后会执行另一个writeFile()函数的异步调用。当readFile()函数执行完成后, 会向任务队列中添加一个任务, 如果没有错误产生, 则执行writeFile()函数, 然后当writeFile()函数执行结束后, 也像任务队列中添加一个任务。
虽然这个模式很灵活, 运行效果也不错, 但是大家想想, 层层嵌套回调函数, 很快大家就会进入回调地狱中, 把自己搞晕了。另外并行执行两个异步操作时, 当两个操作都结束时通知你; 又或者同时进行两个异步操作, 只取优先完成的结果。这种情况, 你就需要跟踪多个回调函数并清理操作, 而promise就能非常好地改进这种情况。
- Promise的基础知识
Promise的意思就是承诺, 承诺在未来的某个时刻完成函数执行, 它相当于异步操作结果的占位符, 它不会去绑定一个事件, 也不会传递一个回调函数给目标函数, 而是让函数返回一个Promise对象
- Promise生命周期
每个promise都会经历一个短暂的生命周期: 先是进行中(pending)的状态, 此时操作尚未完成, 所以它也是未处理(unsettled)的; 一旦异步操作执行结束, Promise则变为已处理(settled)状态,已处理状态包括fulfilled(已成功)和rejected(已失败)两种情况。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就变为我们上面说的已处理(settled)状态。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
- 基本用法
Promise对象是一个构造函数,用来生成Promise实例。
下面代码创造了一个Promise实例。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。下面是一个promise实例
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
上面代码中,Promise 新建后立即执行,所以首先输出的是Promise。then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以会先执行Hi, resolved最后输出。
this
this就是call的第一个参数, 所谓参数, 只有在调用的时候才能传进去, 因此this作为参数, 只有在调用的时候才知道绑定对象
- 被当做对象的方法调用
如果该函数是被当做某一个对象的方法,那么该函数的this指向该对象
let obj = {
foo: function(){
console.log(this)
}
}
let bar = obj.foo;
obj.foo(); //this是obj
bar(); //this是window
var john = {
firstName: "John"
}
function func() {
alert(this.firstName + ": hi!")
}
john.sayHi = func
john.sayHi() // this = john
这里有一点值得注意,当一个对象的方法被取出来赋值给一个变量时,该方法变为函数触发,this指向window或underfind(严格模式)。
- 函数之内调用
当函数中有 this,其实就意味着它被当做方法调用,之间调用相当于把他当做window对象的方法,this指向window,值得注意的是ES5其实是规定这种情况this=undefined的,只浏览器大多还是按照老的方法执行(本人在最新版的Chrome,Safari,Firefox中测试都指向window(201607)),在火狐下使用严格模式指向undefined;
func()
function func() {
alert(this) // [object Window] or [object global] or kind of..
}
为了传递this,()之前应该为引用类型,类似于obj.a 或者 obj['a'],不能是别的了。
这里还存在一个小坑,当对象的方法中还存在函数时,该函数其实是当做函数模式触发,所以其this默认为window(严格模式下为undefined)解决办法是给该函数绑定this。
var numbers = {
numberA: 5,
numberB: 10,
sum: function() {
console.log(this === numbers); // => true
function calculate() {
// this is window or undefined in strict mode
console.log(this === numbers); // => false
return this.numberA + this.numberB;
}
return calculate();
}
};
numbers.sum(); // => NaN or throws TypeError in strict mode
var numbers = {
numberA: 5,
numberB: 10,
sum: function() {
console.log(this === numbers); // => true
function calculate() {
console.log(this === numbers); // => true
return this.numberA + this.numberB;
}
// use .call() method to modify the context
return calculate.call(this);
}
};
numbers.sum(); // => 15
- 在new中调用
一个引用对象的变量实际上保存了对该对象的引用,也就是说变量实际保存的是对真实数据的一个指针。使用new关键字时this的改变其实有以下几步:
- 创建 this = {}.
- new执行的过程中可能改变this,然后添加属性和方法;
- 返回被改变的this.
function Animal(name) {
this.name = name
this.canWalk = true
}
var animal = new Animal("beastie")
alert(animal.name)
需要注意的是如果构造函数返回一个对象,那么this指向返回的那个对象;
function Animal() {
this.name = 'Mousie';
this.age = '18';
return {
name: 'Godzilla'
} // <-- will be returned
}
var animal = new Animal()
console.log(animal.name) // Godzilla
console.log(animal.age)//undefined
这里需要注意的是不要忘记使用new,否则不会创建一个新的函数。而是只是执行了函数,相当于函数调用,this其实指向window
function Vehicle(type, wheelsCount) {
this.type = type;
this.wheelsCount = wheelsCount;
return this;
}
// Function invocation
var car = Vehicle('Car', 4);
car.type; // => 'Car'
car.wheelsCount // => 4
car === window // => true
- 明确调用this,使用call和apply
第一个参数将作为this的指代对象,之后的参数将被作为函数的参数,解决方法是使用bind。
function Animal(type, legs) {
this.type = type;
this.legs = legs;
this.logInfo = function() {
console.log(this === myCat); // => true
console.log('The ' + this.type + ' has ' + this.legs + ' legs');
};
}
var myCat = new Animal('Cat', 4);
// logs "The Cat has 4 legs"
setTimeout(myCat.logInfo.bind(myCat), 1000);
// setTimeout??
var john = {
firstName: "John",
surname: "Smith"
};
function func(a, b) {
alert( this[a] + ' ' + this[b] );
}
func.call(john, 'firstName', 'surname'); // "John Smith"
至于apply,其只是以数组的方传入参数,其它部分是一样的,如下:
func.call(john, 'firstName', 'surname');
func.apply(john, ['firstName', 'surname']);
它们也可用于在 ES5 中的类继承中,调用父级构造器。
function Runner(name) {
console.log(this instanceof Rabbit); // => true
this.name = name;
}
function Rabbit(name, countLegs) {
console.log(this instanceof Rabbit); // => true
// 间接调用,调用了父级构造器
Runner.call(this, name);
this.countLegs = countLegs;
}
var myRabbit = new Rabbit('White Rabbit', 4);
myRabbit; // { name: 'White Rabbit', countLegs: 4 }
- bind()
对比方法 .apply() 和 .call(),它俩都立即执行了函数,而 .bind() 函数返回了一个新方法,绑定了预先指定好的 this ,并可以延后调用。.bind() 方法的作用是创建一个新的函数,执行时的上下文环境为 .bind() 传递的第一个参数,它允许创建预先设置好 this 的函数。
var numbers = {
array: [3, 5, 10],
getNumbers: function() {
return this.array;
}
};
// Create a bound function
var boundGetNumbers = numbers.getNumbers.bind(numbers);
boundGetNumbers(); // => [3, 5, 10]
// Extract method from object
var simpleGetNumbers = numbers.getNumbers;
simpleGetNumbers(); // => undefined or throws an error in strict mode
使用.bind()时应该注意,.bind() 创建了一个永恒的上下文链并不可修改。一个绑定函数即使使用 .call() 或者 .apply()传入其他不同的上下文环境,也不会更改它之前连接的上下文环境,重新绑定也不会起任何作用。只有在构造器调用时,绑定函数可以改变上下文,然而这并不是特别推荐的做法。
- 箭头函数
箭头函数并不创建它自身执行的上下文,使得 this 取决于它在定义时的外部函数。箭头函数一次绑定上下文后便不可更改,即使使用了上下文更改的方法:
var numbers = [1, 2];
(function() {
var get = () => {
console.log(this === numbers); // => true
return this;
};
console.log(this === numbers); // => true
get(); // => [1, 2]
// 箭头函数使用 .apply() 和 .call()
get.call([0]); // => [1, 2]
get.apply([0]); // => [1, 2]
// Bind
get.bind([0])(); // => [1, 2]
}).call(numbers);
这是因为箭头函数拥有静态的上下文环境,不会因为不同的调用而改变。因此不要使用箭头函数定义方法
function Period (hours, minutes) {
this.hours = hours;
this.minutes = minutes;
}
Period.prototype.format = () => {
console.log(this === window); // => true
return this.hours + ' hours and ' + this.minutes + ' minutes';
};
var walkPeriod = new Period(2, 30);
walkPeriod.format(); // => 'undefined hours and undefined minutes'