js的新标准从提案变成正式总共经历5个阶段
交换
[a,b] = [b,a]
提取
let {a,b,c} = json;
传入函数的参数解构
function func({a,b}){}
func({a:1,b:2});
传入函数参数自带默认值
function func(json={
a:1,
b:2
}){
return true;
}
js允许采用\uxxx
的形式表示一个字符,其中xxxx表示字符串码点。
"\u0061" // "a"
但是这种表示法只限于\u0000 ----\uFFFF之间的字符。超出这个范围的必须用两个字节表示
"\uD842\uDFB7" \"𠮷"
ES6对此进行了改进
"\u{42}\u{43}\u{44}"
JavaScript内部,字符串以UTF-16的形式储存,每个字符固定2个字节,对于那些需要四个字节储存的字符,JavaScript认为他们是占两个字节。
ES6提供codePointAt(0)的方法,能正确处理4个字节的字符,并返回一个字符的码点。
ES7推出字符串不全长度功能,如果字符串长度未达到,会自动在头尾补全, 'x'.padStart(5,'ab') // 'ababx'; 'x'.padEnd(4,'ab') // 'xaba';
`
hi,i am crazy
hihih${ iamchangevalue }
`
下面例子,一个通过模板字符串生成真正正式模板的实例 各种奇淫技巧我就不一一详述。
写代码经常遇到一个这样的问题,他没有forEach的方法,我们运用扩展符号,轻松将他转为数组。
var list = document.querySelectorAll('div');
var arr = [...list];
a.name即返回函数名字
a = function func(a){}
a.name // 'func'
构造函数的名字
(new Function).name // "anonymous"
bind绑定的函数的名字,返回的是 'bound
a = function func(a){}
a.bind({}).name // "bound func"
var func = () => {}; // 这是最常见的箭头函数,这里不再复述
当返回的是一个对象的时候呢??意想不到的是,报错了,因为函数默认识别{作为一段代码块,而你则把{当作对象的开头,所以这种情况下必须加上小括号。(),同时箭头函数可以配合变量结构一起使用,相当让人愉悦的一段代码。
var getObject = ({a,b}) => {a:1,b:2}
返回的对象和传入的对象是一样的值,但是对象早已不是之前的对象,用这种写法来写函数式编程应该会非常舒适。
let obj = ({a,b}) => ({a,b});
obj({a:1,b:3});
[1,2,3].map(function(e){
return e*e;
})
[1,2,3].map(e=>e*e);
总结一下,箭头函数最主要的效果是简化回调函数,但这需要建立在对函数熟悉的基础上。但是使用过程中要注意以下几点:
function functhis() {
return this;
}
const arrowthis = () => this;
console.log(functhis()); // window
console.log(arrowthis()); // {}
const a = function() {
console.log(functhis()); // window
console.log(arrowthis()); // {}
};
a();
function afunc(){} // undefined
new afunc()
afunc {}
afunc = () => {}
() => {}
new afunc() // VM19499:1 Uncaught TypeError: afunc is not a constructor .at <anonymous>:1:1
call和普通调用不同,call内传入的参数是就算函数内的this,而此处箭头函数,this永远指向函数本身。而如果是普通函数,this指向window,严格模式下,this指向undefined.,不错的es6。他让js变得简单优雅。。。
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
foo();
foo.call({ id: 1 });
下面代码,this永远指向函数本身,因为他是箭头函数。
const handler = {
id: '123456',
init() {
document.addEventListener(
'click',
e => this.dosomething(e.type), false,
);
},
dosomething(type) {
console.log(`handle ${type} for ${this.id}`);
},
};
这里定时器内部箭头函数的this同样指向对象本身
function Timer() {
this.sec = 0;
setInterval(() => this.sec++, 100);
}
const timer = new Timer();
setInterval(() => {
console.log(timer.sec);
}, 300);
this指向的固定化,为什么呢,因为箭头函数内部根本没有this,所以this永远指向对象本身。正因为如此,所以箭头函数同样不能做构造函数。
箭头函数可以用以将this指向函数本身,这大大简便了js绑定,举个例子react官方说的一个绑定例子
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
react采用this.handleClick.bind(this)
,这样的绑定函数会返回一个函数,而返回的新函数通过bind绑定的新函数,下面看mdn对于bind的定义。返回的新函数this永远指向,意思就是,bind函数传入的第一个参数,就算新函数的this,无论你怎么调用,都是这个this,这和箭头函数的核心含义是不谋而合的。
下面是react官方的第二种写法,省区了bind步骤,就是因为使用了箭头函数。
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
// Warning: this is *experimental* syntax.
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
箭头函数可以绑定this,大大减少了显示绑定this对象的方法(call,apply,bind),但是es7提出了新的提案,::
用来绑定
fo0::bar
// 等同于
bar.bind(foo)
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
说的就算高阶函数
let highFunc = (arr) => {
let a = 1;
let b = 2;
return g(a+b);
}
js的函数可以说是非常灵活的可以在任何地方被调用,包括if(内部){},关键不在于函数书写形式,关键在于所返回的是函数还是对象还是字符串,或者根本没有return,func()()()
只要返回的是所需要的东西即可,不管你后面跟了几个括号。
具体可以了解相关调用栈(call stack #17 )由于函数尾部是一个新的函数,所以调用它的函数会被先存入调用栈,等待新函数被找到之后,新函数也会被放入调用栈,然后调用栈按照FILO(先入后出)队列原则,依次执行。
let highFunc = (arr) => {
let a = 1;
let b = 2;
return g(a+b);
}
highFunc()
// 等价于
function highFunc(){
return g(3);
}
// 等价于
g(3);
上面代码,如果g不是尾调用,函数f就需要保存内部变量m,n,以及g的调用位置,。但是当g被调用之后,函数f就结束了,所以f(x)函数完全可以被删除。 上面说的就是尾调用优化,可以节省部分内存??
函数调用自身称之为递归,在尾部调用自身,称之为尾递归, 下面的是递归函数,调用栈最大承受量为11370次,容易发生栈溢出效应。其复杂度为O(n)
function factorial(n) {
if (n === 1) return 1;
return n + factorial(n - 1);
}
console.log(factorial(11370));
但是对于下面这个函数,尾递归由于全程只发生一个调用栈,复杂度为O(1),当然此刻依然是调用栈溢出,因为只是es6对尾递归进行了优化,你需要开启chrome实验室新功能。
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n + total);
}
console.log(factorial(11300, 1));
proxy用于修改某些操作的默认行为,等同于在语言层面做一些事,所以这属于元编程。,即对语言进行编程。
const obj = new Proxy({}, {
get(target, key, receiver) {
console.log(`getting ${key}`);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`setting ${key}`);
return Reflect.get(target, key, value, receiver);
},
});
上面的代码对一个空对象做了一层拦截层,重定义了读取属性的set和设置set行为。这里暂时先不解释其具体做法。看看运行结果。
obj.count = 1; // setting count
++obj.count; // setting count, getting count
ArrayBuffer对象,TypedArray,DataView视图,是JavaScript操纵二进制数据的一个接口, 二进制数组由三个对象组成
数据类型 | 字节长度 | 含义 | 对应c语言 |
---|---|---|---|
int8 | 1 | 8位带符号整数 | signed char |
Uint8 | 1 | 8位不带符号整数 | unsigned char |
Uint8C | 1 | 8位不带符号整数 | unsigned char |
Int16 | 2 | 16位带符号整数 | short |
Uint16 | 2 | 16位不带符号整数 | unsigned short |
Int32 | 4 | 32位带符号整数 | int |
Uint32 | 4 | 32位不带符号整数 | unsigned int |
Float32 | 4 | 32位浮点数 | float |
Float64 | 8 | 64位浮点数 | double |
很多浏览器API用到了二进数组操作二进制数据,下面是其中的几个。
ArrayBuffer对象代表储存二进制数据的一段内存,他不能直接读/写,只能通过视图来读写。视图的作用是指以指定格式解读二进制数据。 ArrayBuffer也是一个构造函数,可以分配一段可以存放的数据的内存区域。 下面这段代码生成了一段32字节的内存区域,每个字节的默认值都是0,可以看到ArrayBuffer构造函数的参数是所需要的内存大小(单位位字节)。
var buf = new ArrayBuffer(32);
buffer
var buf = new ArrayBuffer(12);
buffer
为了读写这段内存,需要为它指定视图。创建DataView视图。需要提供ArrayBuffer对象实例作为参数
var buf = new ArrayBuffer(32);
dataView = new DataView(buffer);
dataView.getInt8(16) // 0
上面代码对一段32字节的内存建立DataView视图,然后以不带符号的8位整数格式读取第16个元素得到0,因为他的所有都是0。
ArrayBuffer对象作为内存区域存放多种类型数据。同一段内存不同数据的不同解读方式,这就叫做视图。ArrayBuffer存在2种视图,一种是TypedArray视图,另一种是DataView视图,前者所有数组成员都是同一个数据类型吗,后者数组成员可以是不同的数据类型。
set作为es6新的一种数据结构,类似于数组,但是他的成员的值是唯一的。没有重复的值。 set的一些方法如下 set.add({})不断添加{},因为{} === {} // false
和set类,但不同之处在于
map结构的目的和基本用法。
目前存在3个ajax访问,当url1和url2同时访问某个接口,只有当两个接口同时拿到数据的时候,url3开始执行去拿某个接口的数据。url1,和url2拿数据的速度未知。可能url1更快,也可能url2更快。只有当2个接口的数据都拿到之后才去拿url3的数据。闲话不多说,上代码
const p1 = new Promise((resolve) => {
setTimeout(() => {
console.log('p1');
resolve();
}, 2000);
});
const p2 = new Promise((resolve) => {
setTimeout(() => {
console.log('p2');
resolve();
}, 3000);
});
const p3 = new Promise((resolve) => {
setTimeout(() => {
console.log('p3');
resolve();
}, 1000);
});
Promise.all([p1, p2]).then(() => {
console.log('values');
p3;
});
遍历器(lterator)就是这样一种接口机制,为各种不同的接口机制提供统一的访问机制。任何数据结构只要有lterator接口,就能遍历循环。 lterator的作用有三种,
lterator的遍历过程是这样的,
每一次调用next方法,都会返回数据结构当前成员的信息。下面定义一个makeIterator函数用来遍历循环
function makeIterator(array) {
let nextIndex = 0;
return {
next() {
return nextIndex < array.length ? {
value: array[nextIndex += 1], done: false,
} : {
value: undefined, done: true,
};
},
};
}
const itt = makeIterator(['a', 'b', 'b', 'b', 'b', 'b', 'b']);
itt.next() // { value: 'b', done: false }
itt.next() // { value: 'b', done: false }
itt.next() // { value: 'b', done: false }
itt.next() // { value: 'b', done: false }
[...new Array(3)] // [undefined,undefined,undefined]
[...'23r3r23r'] // ['a','b'....]
还有菊花函数 function* (){ yield [1,2,3,4,5] }
const generator = function* () {
yield 1;
yield* [2, 3, 4];
yield 5;
};
const iterator = generator();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
======得到的值======
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 4, done: false }
{ value: 5, done: false }
{ value: undefined, done: true }
上面的[]数组内部内容被自动解构,他后面如果跟的是一个可遍历的结构,他会被用于遍历器的接口。
14.4 字符串的Iterator接口 字符串本身其实也可以遍历,他也具有遍历接口
str = '23r23r'[Symbol.iterator]();
str.next(); // {value: "2", done: false}
str.next(); // {value: "3", done: false}
str.next(); // {value: "r", done: false}
str.next(); // {value: "2", done: false}
str.next(); // {value: "3", done: false}
str.next(); // {value: "r", done: false}
str.next(); // {value: undefined, done: true}
[...'wefe233'] // ["w", "e", "f", "e", "2", "3", "3"]
Set 和 Map结构具有Iterator接口,可以直接使用for...of循环
const es6 = new Map();
es6.set('eee',1);
es6.set('fff',1);
es6.set('ggg',1);
es6.set('hhh',1);
es6.set('iii',1);
for(var [name,value] of es6){
console.log(name,value)
}
// eee 1
// fff 1
// ggg 1
// hhh 1
// iii 1
下面then会生成2个promise,先new一个promise,then里面再生成一个新的promise,通过不断的生成一个新的promise,而产生链式调用,这个和jQuery是不谋而合的。
const getJsono = new Promise((resolve, reject) => {
resolve(x + 1);
});
getJsono
.then((json) => {
console.log(json);
})
.catch((err) => {
console.log(err);
});
多个rpomise一起完成。 在进行下一步。 下面请看async/await,虽然他也只是基于Generator的语法糖,但是配合promise少不了它。
异步编程对于js这门语言非常重要。js是单线程,如果没有异步,就会卡死。 es诞生之前,异步编程大概有以下几种方案
所谓异步就算将一段任务分成两段,先执行一段,然后转而执行其他的任务,等好之后,又回过头来重新执行之前的代码。 下面的就算以回调的方式实现异步代码。但是他会先 输出 123,然输出文件内容,这是典型的异步代码,但是async/await绕来绕去,原本js阻塞的同步代码被写成异步,现在async/await有将他们变成同步的 阻塞代码,醉醉醉。同步=>异步=>同步
fs.readFile('./test/index.html', 'utf-8', (err, data) => {
console.log(data);
});
console.log('123');
我个人觉得这种渣翻译非常容易引起误解,所谓回调函数就算把第二段单独写入一个函数中,等到重写执行该函数的时候重新调用,所以callback直译过来应该是重新调用的意思,而不是所谓的回调函数。
回调函数本身并没有问题,问题在于多个回调函数嵌套,假定读取A文件后再读取B文件,代码如下
fs.readFile('./test/index.html', 'utf-8', (err, data) => {
fs.readFile('./test/index.html', 'utf-8', (err, data1) => {
console.log(data, data1);
});
});
为了避免多重嵌套,所以出现了异步函数。然而我觉得这个好失败。怪不得TJ大神要转去玩GO, 17.2 Generator 函数
传统的编程语言早已有异步解决方案,(其实是多任务的解决方案)。其中有一种叫做协程(coroutine),意思是多个线程相互协作,完成异步任务。 协程有点像函数,又有点像线程,流程如下:
上面的协程A就算异步任务。因此他分成2段执行。
他会将函数执行权交出去,即暂停执行。需要暂停的地方用yield,
function* gen(x){
var y = yield x+2;
return y;
}
var g = gen(1);
g.next(); // {value:3,done,false}
g.next(); // {value:undefined,done,true}
上述代码,调用Generator函数会返回一个内部指针,g,这是生成器函数不同于普通函数的地方,他返回的指针每次调用next都会指向下一个yield,也就是返回一个对象。 Generator可以暂停执行,这是它可以封装异步函数的根本原因。
传入的是一个函数,那么传入的函数等到传入的一刻才计算。尾递归就是一个例子,但是尾递归存在栈溢出的性能问题。
#####基本用法 co模块是TJ发布的一个小工具。用于generator函数自动执行。
var gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
co模块可以让你不用编写Generatar函数的执行器。
var co = require('co');
co(gen);
上面的代码中,Generator函数只要传入co函数就会自动执行。 co函数返回一个Promise对象,因此可以用then方法添加回调函数。
co(gen).then(function(){
console.log('Generator 函数执行完成');
})
上面的代码中,等到Generator函数执行结束,就会输出一行提示。 ####co模块的原理 为什么co模块可以自动执行Generator函数? 前面说过,Generator就是一个异步执行函数的容器。它自动执行需要一种机制,当异步操作有了结果能够自动交回执行权。
co模块其实就是将两种自动执行器(Thunk函数和Promise对象)包装成了一个模块。使用co的前提条件是,Generator函数的yield命令后面只能是Thunk函数或Promise对象。 下面讲一下基于generator函数的自动执行器。这是理解co函数的关键。 ####基于Promise对象的自动执行 还是沿用上面的例子。首先把fs模块的readFile方法包装成一个Promise对象。
const readFile = fileName => new Promise(((resolve, reject) => {
fs.readFile(fileName, (err, data) => {
if (err) reject(err);
resolve(data);
});
}));
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
const g = gen();
g.next().value.then((data) => {
g.next().value.then((data) => {
g.next(data);
});
});
手动执行的方法其实就算用then方法层层添加回调函数,理解这一点,就能写出自动执行函数,
function run(gen) {
const g = gen();
function next(data) {
const result = g.next(data);
if (result.done) return result.value;
result.value.then((data) => {
next(data);
});
}
}
上面的代码中,只要Generator函数还没执行到最后异步,next函数就调用自身以实现自动执行。 ####co模块的源码 co就是上面这个自动执行的扩展,他的源码只有十几行,非常简单。 首先,co函数接受Generator函数作为参数,返回一个Promise对象。
function co(gen){
var ctx = this;
return new Promise(function(resolve,reject)=>{})
}
在返回Promise对象中,co先检查参数gen是否为Generator函数。如果是,就执行该函数,得到内部指针对象;如果不是就返回,并将promise对象的状态改为Resolve。
function co(gen) {
const ctx = this;
return new Promise((resolve, reject) => {
if (typeof gen === 'function') gen = gen.call(ctx);
if (!gen || typeof gen.next !== 'function') return resolve(gen);
onFullfilled(res);
function onFullfilled(res) {
let ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
});
}
最后最关键的就算next函数
function next(ret) {
if (ret.done) return resolve(ret.value);
const value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFullfilled, onRejected);
return onRejected(new TypeError(`You may only yield a function, promise, generator, array, object, but not the following object was passed: ${String(ret.value)}"`));
}
上面代码中,next函数内部的代码一共只有4行命令。 第一行,检查当前是否为Generator函数最后一步,如果是就返回。 第二行,确保每一步的返回值是Promise对象。 第三行,使用then方法为返回值加上回调函数,然后通过onFullfilled函数再次调用next函数。 第四行,在参数不符合要求的情况下,将promise对象状态改为Rejected,从而终止执行,
co支持并发的异步操作,即允许某些操作同时进行,等到他们完成,才进行下一步。这时要把并发的操作都放在数组或者对象里面,跟在yield语句后面。
// 数组写法
co(function* () {
const res = yield [
Promise.resolve(1),
Promise.resolve(2),
];
});
// 对象写法
co(function* () {
const res = yield {
0: Promise.resolve(1),
0: Promise.resolve(2),
};
});
// 下面是一个例子
co(function* (){
var values = [n1,n2,n3];
yield values.map(somethingAsync);
})
function* somethingAsync(x){
// do something async
reyurn y;
}
上面代码允许并发3个somethingAsync异步操作,等到他们全部完成,才能进行下一步。
他就是Generator函数的语法糖 前文有一个Generator函数,一次读取2个文件
const readFile = fileName => new Promise(((resolve, reject) => {
fs.readFile(fileName, (err, data) => {
if (err) reject(err);
resolve(data);
});
}));
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
//写成async函数就是下面这样
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
其实就只是将*号换成async,将yield换成await的语法糖。 async函数对Generator函数进行了改进,以下4点体现
var result = asyncReadFile();
2.上面代码调用了asyncReadFile函数,然后他就会自动执行,输出结果,完全不想Generator函数需要调用next方法,或者co模块,才能执行。 3.更好的语义。async和await比起yield语义更加清楚。 4.更广的适用性,co模块约定,yield命令后面只能是Thunk函数或者promise对象,而async函数的await后面能是promise对象和原始类型的值,无论数字对象字符串等。 5.返回值Promise。async函数的返回值是Promise对象,这比Generator函数的返回值是Lterator对象方便多了,你完全可以用then方法指定下一步。 进一步说明,async函数完全可以看作由多个异步操作包装成一个Promise对象,而await命令就算内部then语法糖。
async函数的实现的实现就是把Generator和自动执行函数包在一个函数中。
async function fn(args){
// ....
}
// 等同于
function fn(args){
return spawn(function*(){
// ...
});
}