1 全局变量和局部变量
1.1 let、const
-
- 在es5中,存在全局作用域和函数作用域,没有块级作用域的概念;
-
- 在es6中,新增了块级作用域,由大括号构成;
-
let
、const
因为暂时性死区的原因,不能在声明前使用。
-
var
在全局作用域下声明变量会导致变量挂载在window
上,其他两者不会。
console.log(aa) // undefined[后面aa已经进行过声明提升]
console.log(bb) // Cannot access 'bb' before initialization
console.log(cc) // Cannot access 'cc' before initialization
var aa = 1
let bb = 1
const cc = 1
console.log(window.aa) // 1
console.log(window.bb) // undefined
console.log(window.cc) // undefined
function test(){ //在一个{}构成的区域块中,let aa导致了在该区域内暂时性死区
console.log(aa)
let aa;
}
test() //Cannot access 'aa' before initialization
1.2 const
const PI = {};
//PI.name='xihua'; //[可以]
PI = {
name:'xiaohua' //直接赋值的报错
}
console.log(JSON.stringify(PI));
1.3 var变量
var没有块级作用域【区别于let有块级作用域】,只有函数作用域。
- 示例:while中不属于函数作用域,aa会被覆盖;而函数作用域中属于私有变量,不会影响外面的变量
var myname = 'aa';
while(true){
var myname = 'bb';
console.log(myname); //bb
break;
}
console.log(myname);//bb
- 示例:函数作用域中,影响aa的值
var aa = 10;
function fn(){
console.log(aa);//undefined
var aa = 20;
console.log(aa);//20
}
fn();
console.log(aa);//10
- 示例:变量提升即把变量声明提升到它所在作用域的最开始的部分。注意只是提升声明,并没有赋值。
函数提升, 创建函数有两种形式,一种是函数声明,另外一种是函数字面量,只有函数声明才有变量提升。
var myname = 'aa';
while(true){
var myname = 'bb';
console.log('1'+this.myname); //1bb
break;
}
function cc(){
var myname = 'cc';
console.log('3' + myname); //3cc
console.log('4' + this.myname); //4bb
}
console.log('2' + myname); //2bb
cc();
结果是: 1bb,2bb,3cc,4bb
注意this默认指向windows;
解析:
function cc(){
var myname = 'cc';
console.log(myname); //cc
console.log(this.myname); //undefined
}
cc()
console.log(this.myname); //undefined
注意,这里的this.myname相当于window.myname,window是一个对象,如果没有myname属性,则返回undefined
function Cc(){
var myname = 'cc';
this.myname = myname;
console.log(myname); //cc
console.log(this.myname); //cc
}
var cc = new Cc();
console.log(cc.myname);//cc
console.log(this.myname);//undefined
- 示例:变量提升,且函数提升优于变量提升;
console.log(a) // ƒ a() {}
var a = 1
function a() {}
function test(){
for(let i=1;i<3;i++){
console.log(i);
}
console.log(i);
}
test();
//1
//2
//报错 i is not defined
//let是for循环中的块级作用域中,所以在for循环中可以获取到i 但是在for循环外面 无法获取到i;
- 示例:var定义的i是全局的,每一次循环,新的值都会覆盖旧值。
var a = [];
for(var i=0;i<5;i++){
a[i] = function(){
console.log(i);
}
}
a[2]();
let的出现很好的解决了
2.闭包问题
(最简单的闭包:当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包。)
function A(){
return function B(){
console.log('from B');
}
}
var C = A();//from B
C();
闭包可以用在许多地方。它的最大用处有两个,
-
- 一个是可以读取函数内部的变量[示例1],
-
- 另一个就是让这些变量的值始终保持在内存中[示例2]。
- 示例:可以读取函数内部的变量
function aa(){
var name = 'xiaohua';
return function bb(){
return name;
}
}
var cc = aa();
console.log(cc()) // xiaohua
- 示例:这些变量的值始终保持在内存中
function aa(){
var num = 0;
return function bb(){
num++;
return num;
}
}
var cc = aa();
console.log(cc())//1
console.log(cc())//2
console.log(cc())//3
console.log(cc())//4
综上,闭包就是在 函数A中 return 一个函数;外面调用 A()();
常见考题:现有如下html结构
<ul>
<li>click me</li>
<li>click me</li>
<li>click me</li>
<li>click me</li>
</ul>
运行如下代码:
var elements=document.getElementsByTagName('li');
var length=elements.length;
for(var i=0; i<length; i++){
elements[i].onclick=function(){
alert(i);
}
}
依次点击4个li标签,哪一个选项是正确的运行结果()?
依次弹出1,2,3,4
依次弹出0,1,2,3
依次弹出3,3,3,3
依次弹出4,4,4,4
//解释一下,之所以先执行 for循环,是因为点击函数相当于异步函数,之后点击标签之后,才会执行onclick事件,所以for循环会先执行。
var elements=document.getElementsByTagName('li');
var length=elements.length;
for(var i=0;i<length;i++){
elements[i].onclick=function(j){
return function() {
alert(j);
};
}(i);
}
//在上述代码中,我们首先使用了立即执行函数将 `i` 传入函数内部,这个时候值就被固定在了参数 `j` 上面不会改变,当下次执行 `timer` 这个闭包的时候,就可以使用外部函数的变量 `j`,从而达到目的。
3.新增类 class
class 后面大写表示类, consructior 表示构造函数,子类中本来没有this,只有子类构造函数中用super函数来调用父类中的 consructior 函数,之后才可以使用this,子类中的this指向子类;这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
class People{
constructor(name,age){
this.name=name;
this.age = age;
}
say(){
console.log(this.name+' is '+this.age);
}
}
class Man extends People{
constructor(name,age){
super(name,age);
this.name = 'man'
}
}
let man1 = new Man('xiaoming',12);
man1.say();
super
,因为这段代码可以看成 People.call(this,name,age)
。
4 this 指针
箭头函数是定义的时候指定的this;普通函数是在调用的时候指定的this;
this跟函数在哪里定义没有关系,函数在哪里调用才决定了this到底引用的是啥。也就是说this跟函数的定义没关系,跟函数的执行有大大的关系。所以,记住,“函数在哪里调用才决定了this到底引用的是啥”。
1. 单独的this,指向的是window这个对象
alert(this); // this -> window
2. 全局函数中的this
function demo() {
alert(this); // this -> window
}
demo();
在严格模式下,this是undefined.
function demo() {
'use strict';
alert(this); // undefined
}
demo();
3. 函数调用的时候,前面加上new关键字【这个同calss类】
所谓构造函数,就是通过这个函数生成一个新对象,这时,this就指向这个对象。
function demo() {
//alert(this); // this -> object
this.testStr = 'this is a test';
}
let a = new demo();
alert(a.testStr); // 'this is a test'
再如:
function fn(){
console.log(this.a);
}
var obj = {
a:2,
fn:fn
}
obj.fn(); //2
最后一个调用该函数的对象是传到函数的上下文对象,如:
function fn() {
console.log( this.a );
}
var obj2 = {
a: 42,
fn: fn
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.fn();//42
注意的是,如果失去绑定,[即赋值给变量类] 则到全局:
function fn() {
console.log( this.a );
}
var obj = {
a: 2,
fn: fn
};
var bar = obj.fn; // 函数引用传递
var a = "全局"; // 定义全局变量
bar(); // "全局"
4. 用call与apply的方式调用函数;比如A.call(B); 使用的是A中的方法,用的是B中的变量
function demo() {
alert(this);
}
demo.call('abc'); // abc
demo.call(null); // this -> window
demo.call(undefined); // this -> window
5. 定时器中的this,指向的是window
setTimeout(function() {
alert(this); // this -> window ,严格模式 也是指向window
},500)
例如:
class Animal {
constructor(){
this.type = 'animal'
}
says(say){
setTimeout(function(){
console.log(this.type + ' says ' + say)
}, 1000)
}
}
var animal = new Animal()
animal.says('hi')
因为setTimeout是延时函数,执行的时候已经脱离对象环境,即在window下进行,所以 this 指向 window,所以结果为: undefined says hi
6. 元素绑定事件,事件触发后,执行的函数中的this,指向的是当前元素
window.onload = function() {
let $btn = document.getElementById('btn');
$btn.onclick = function(){
alert(this); // this -> 当前触发
}
}
7. 函数调用时如果绑定了bind,那么函数中的this指向了bind中绑定的元素
window.onload = function() {
let $btn = document.getElementById('btn');
$btn.addEventListener('click',function() {
alert(this); // window
}.bind(window))
}
let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // => ?
可以从上述代码中发现,不管我们给函数 bind
几次,fn
中的 this
永远由第一次 bind
决定,所以结果永远是 window
。
8. 对象中的方法,该方法被哪个对象调用了,那么方法中的this就指向该对象
let name = 'finget'
let obj = {
name: 'FinGet',
getName: function() {
alert(this.name);
}
}
obj.getName(); // FinGet
let fn = obj.getName;
fn(); //finget this -> window
笔试题
var x = 20;
var a = {
x: 15,
fn: function() {
var x = 30;
//console.log(this.x),这个是15
return function() {
return this.x
}
}
}
console.log(a.fn());
console.log((a.fn())());
console.log(a.fn()());
console.log(a.fn()() == (a.fn())());
console.log(a.fn().call(this));
console.log(a.fn().call(a));
答案
1.console.log(a.fn());
对象调用方法,返回了一个方法。
function() {return this.x}
2.console.log((a.fn())());
a.fn()返回的是一个函数,()()这是自执行表达式。this -> window
20
3.console.log(a.fn()());
a.fn()相当于在全局定义了一个函数,然后再自己调用执行。this -> window
20
4.console.log(a.fn()() == (a.fn())());
true
5.console.log(a.fn().call(this));
这段代码在全局环境中执行,this -> window
20
6.console.log(a.fn().call(a));
this -> a
15
类似的
var x = 20;
var a = {
x: 15,
fn: function() {
var x = 30;
console.log(this.x)
}
}
console.log(a.fn());//15
5 箭头函数
箭头函数是定义的时候指定的this;普通函数是在调用的时候指定的this;
箭头函数中this倒底指向谁?一句话,箭头函数内的this就是箭头函数外的那个this! 为什么?因为箭头函数没有自己的this。
箭头函数的this看定义他的时候,他的外层有没有函数
有:外层函数的this就是箭头函数的this
无:箭头函数的this就是window
obj = {
age:18,
getAge: ()=>console.log(this.age)
}
obj.getAge()
//undefined 定义的时候外层没有函数,指向window
obj = {
age:18,
getAge: function(){
print = ()=>console.log(this.age);
print()
}
}
obj.getAge()
//18 定义的时候外层函数的this就是箭头函数的this
例如:
class Animal {
constructor(){
this.type = 'animal'
}
says(say){
setTimeout( () => {
console.log(this.type + ' says ' + say)
}, 1000)
}
}
var animal = new Animal()
animal.says('hi') //animal says hi
再如,第一个用的是function,this指向的是调用者 obj,而第二个示例用的是箭头函数,this指向定义时的对象window
示例1:
function fn() {
console.log(this.a); //调用的时候,obj来调用的,this指向obj
}
var obj = {
a:2,
fn:fn
}
a =3 ;
obj.fn(); //2
示例2:
let fn = ()=>{
console.log(this.a); //定义的时候,外侧没有函数,this指向window
}
var obj = {
a:2,
fn:fn
}
a =3 ;
obj.fn(); //3
//
class Animal {
constructor(){
this.name = 'animal'
}
says(){
console.log(this); //该 animal 对象
setTimeout(()=>{
console.log(this);
},3000)
}
}
let obj1 = new Animal();
console.log(obj1.says());//该 animal 对象
//
function People (){
this.name = "people";
this.says = function(){
console.log(this); //该 People 对象
}
}
let obj2 = new People();
console.log(obj2.says());
let obj = {
age:18,
getAge: function(){
console.log(this); //该 obj 对象
setTimeout(()=>{
console.log(this);
},2000)
}
}
obj.getAge()//该 obj 对象
//
obj = {
age:18,
getAge: ()=>{
console.log(this.age) //指向window
}
}
obj.getAge()
6. 解构赋值/ 模板字符串
`${name}的岁数是${age}`
6.1 解构数组:
let arr = [1,2,3];
let [a,b,c] = arr;
console.log(a,b,c); //1 2 3
let [,,a,,b] = [1,2,3,4,5];
console.log(a,b); // 3 5
let [a,...reset] = [1,2,3,4,5];
console.log(a,reset); //1,[2,3,4,5]
6.2 可以设置默认值
let obj = {
name:"zhuangzhuang"
};
let {name,age = 18} = obj;
console.log(name,age);//避免age为undefined
6.2 使用别名
如果使用别名,则不允许再使用原有的解构出来的属性名,看以下举例则会明白:
let p1 = {
"name":"zhuangzhuang",
"age":25
}
let {name:aliasName,age:aliasAge} = p1;//注意变量必须为属性名
console.log(aliasName,aliasAge);//"zhuangzhuang",25
console.log(name,age);//Uncaught ReferenceError: age is not defined
注意:
只报错age,不报错name,这说明其实name是存在的,那么根据js的解析顺序,当在当前作用域name无法找到时,会向上找,直到找到window下的name,而我们打印window可以发现,其下面确实有一个name,值为“”,而其下面并没有属性叫做age,所以在这里name不报错,只报age的错。类似name的属性还有很多,比如length等。
对于复杂的数据结构,获取内部数据,按照原来的数据结构取值即可:
let metaData={
title:'abc',
test:[{
name:'test',
desc:'description'
}]
}
let {title,test:[{name}]}=metaData;
console.log(title,name);
又比如,获取test是个对象中的name:
let metaData2={
title:'abc',
test:{
name:'test',
desc:'description'
}
}
let {title,test:{name}}=metaData2;//test在原数据中就是
console.log(title,name);
默认参数
function animal(type = 'cat'){
console.log(type)
}
animal();// cat
7. export等规范
AMD,CMD,CommonJS,ES6的比较:
框架 | 实现 | 同步/异步 |
---|---|---|
AMD | RequireJS | 异步 |
CMD | SeaJS | 同步 |
nodeJs | CommonJS | |
ES6 | import | 异步 |
【7.1】 AMD---异步模块定义
是一个概念,RequireJS 是对这个概念的实现。好比JavaScript语言是对ECMAScript规范的实现。
RequireJS:是一个AMD框架,可以异步加载JS文件,按照模块加载方法,通过define()函数定义,第一个参数是一个数组,里面定义一些需要依赖的包,第二个参数是一个回调函数,通过变量来引用模块里面的方法,最后通过return来输出。是一个依赖前置、异步定义的AMD框架(在参数里面引入js文件),在定义的同时如果需要用到别的模块,在最前面定义好即在参数数组里面进行引入,在回调里面加载。
//被引用的文件是 used.js
define('used.js',function(){
return 'A is a cat'
})
require(['moduleA', 'moduleB', './used.js'], function (moduleA, moduleB, lib){
function foo(){
lib.log('hello');
}
return {
foo:foo
}
});
【7.2】CMD----是SeaJS的一个标准
是SeaJS在推广过程中对模块定义的规范化产出,是一个同步模块定义,是SeaJS的一个标准,SeaJS是CMD概念的一个实现,SeaJS是淘宝团队提供的一个模块开发的js框架.
html中使用方式
seajs.use('./../js/index', function (init) {
init();
});
define(function(require, exports, module) {
require('./calendar.js');//使用require的方式引入依赖文件
var init = function() {
//xxxx
}
return init;
//exports.init = function() {};
}
通过define()定义,没有依赖前置,通过require加载jQuery插件,CMD是依赖就近,在什么地方使用到插件就在什么地方require该插件,即用即返,这是一个同步的概念
【7.3】CommonJS----node.js后端使用的规范
CommonJS规范---是通过module.exports定义的,在前端浏览器里面并不支持module.exports, 通过node.js后端使用的。Nodejs端是使用CommonJS规范的,前端浏览器一般使用AMD、CMD、ES6等定义模块化开发的.前端的webpack也是对CommonJS原生支持的
由于 CommonJS 是同步加载模块的,在服务器端,文件都是保存在硬盘上,所以同步加载没有问题,但是对于浏览器端,需要将文件从服务器端请求过来,那么同步加载就不适用了,所以,CommonJS是不适用于浏览器端的。
//被调用的文件 used.js
module.exports={
//变量
//函数
}
使用require引用
//调用的文件 index.js
var animal = require('./used.js');
【7.4】ES6----web前端使用的
方法一:使用 export defalut 导出默认变量或函数
//被调用的文件 used.js
export defalut xxx;
//调用的文件 index.js--被调用的文件使用的 default 默认导出
import animal from './used.js'
方法二:使用 export 导出对象:
//被调用的文件 used.js
export {
sum,
add
}
//调用的文件 index.js
import {add} from './used.js'
方法三: 修改变量名字
import animal, { say, type as animalType } from './content'
【7.4】ES6 模块与 CommonJS 模块的差异
它们有两个重大差异。
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。
而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
下面重点解释第一个差异。
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件lib.js的例子。
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
上面代码输出内部变量counter和改写这个变量的内部方法incCounter。然后,在main.js里面加载这个模块。
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
上面代码说明,lib.js模块加载以后,它的内部变化就影响不到输出的mod.counter了。这是因为mod.counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
get counter() {
return counter
},
incCounter: incCounter,
};
上面代码中,输出的counter属性实际上是一个取值器函数。现在再执行main.js,就可以正确读取内部变量counter的变动了
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
还是举上面的例子。
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
8. Symbol类型
- 8.1 定义
let s2 = Symbol('another symbol')
console.log(s2)
//Symbol(another symbol)
- 8.2 特性
1】每个Symbol实例都是唯一的。因此,当你比较两个Symbol实例的时候,将总会返回false:
let s1 = Symbol()
let s2 = Symbol('another symbol')
let s3 = Symbol('another symbol')
s1 === s2 // false
s2 === s3 // false
应用场景1】:使用Object.keys()或者for...in来枚举对象的属性名,无法获取到 Symbol定义的属性。我们可以把一些不需要对外操作和访问的属性使用Symbol来定义。
let obj = {
[Symbol('name')]: '一斤代码',
age: 18,
title: 'Engineer'
}
Object.keys(obj) // ['age', 'title']
for (let p in obj) { // p即是key;
console.log(p) // 分别会输出:'age' 和 'title'
}
Object.getOwnPropertyNames(obj) // ['age', 'title']
//下面两个方法可以获取到Symbol
// 使用Object的API
Object.getOwnPropertySymbols(obj) // [Symbol(name)]
// 使用新增的反射API
Reflect.ownKeys(obj) // [Symbol(name), 'age', 'title']
补充:for of 和 for in 的区别
let aa = [12,'address',{
name:'xiaohua',
age:21
}]
for(let key of aa){
console.log(key);
}
//of循环数组;in循环对象或者数组;
//记忆点: of 和 for 都有 o;for用来循环数组,所以of也用来循环数组
【应用场景2】:定义一些常量,比如redux中的 actionType,这样不用像以前一样,为每个常量定义名字:
原来:
const TYPE_AUDIO = 'type_audio'
const TYPE_VIDEO = 'type_video'
const TYPE_IMAGE = 'type_image'
现在:
const TYPE_AUDIO = Symbol()
const TYPE_VIDEO = Symbol()
const TYPE_IMAGE = Symbol()
【应用场景3】:使用Symbol定义类的私有属性/方法
参考文章:[1]理解和使用ES6中的Symbol:https://www.jianshu.com/p/f40a77bbd74e
9. Iterator 和 for...of循环
JavaScript原有的四种表示'集合'的数据结构,Object、Array、Set、Map。
遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署了Iterator接口,就可以完成遍历操作。
Iterator 的作用有三个:
一是为各种数据结构,提供一个统一的、简便的访问接口;
二是使得数据结构的成员能够按某种次序排列;
三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
let arr = ['hello','world'];
let map = arr[Symbol.iterator]();
console.log(map.next());
console.log(map.next());
console.log(map.next());
// 返回结果
// {value: "hello", done: false}
// {value: "world", done: false}
// {value: undefined, done: true}
for...of 就是调用的数组或者对象的 Symbol.iterator 方法,数组内置了Symbol.iterator 方法
但是由于对象内置结构比较复杂,所以没有定义,这个方法需要自定义。
let obj = {
start:[1,3,2],
end:[7,9,8],
[Symbol.iterator](){
let self = this;
let index = 0;
let arr = self.start.concat(self.end);
let len = arr.length;
return {
next(){
if(index<len){
return {
value:arr[index++],
done:false //false表示没有结束
}
}else{
return {
value:arr[index++],
done:true
}
}
}
}
}
}
console.log(obj[Symbol.iterator]().next()); //{value: 1, done: false}
for(let value of obj){
console.log(value);
}
// 1 3 2 7 9 8
10. Generator
基本定义
// genertaor基本定义
let tell=function *(){
yield 'a';
yield 'b';
return 'c'
};
let k=tell();
console.log(k.next());
console.log(k.next());
console.log(k.next());
console.log(k.next());
示例题:
function *foo(x) {
let y = 2 * (yield (x + 1))
let z = yield (y / 3)
return (x + y + z)
}
let it = foo(5)
console.log(it.next())
console.log(it.next(12))
console.log(it.next(13))
// => {value: 6, done: false}
// => {value: 8, done: false}
// => {value: 42, done: true}
解析:
yield相当于return返回;
let it = foo(5) 并不会执行 yield,会返回一个Iterator实例, 然后再执行Iterator实例的next()方法
如果给next方法传参数, 那么这个参数将会作为上一次yield语句的返回值;
每次执行 it.next()时,都相当于执行对应的 yield();
所以 it.next() 时,会返回在 let y = 2 * (yield (x + 1))这句话的,yield (x + 1), foo传进来的参数 5 ,则
返回 5+1 = 6;【注意此时不用管函数表达式 let y = 2 * (yield (x + 1)) ,只用考虑 yield (x + 1) 】
第二次执行 it.next(12);会从上一次暂停的位置开始 let y = 2 * (yield (x + 1))
且 传参12 等于yield (x + 1),所以 y = 2* 12 = 24;且返回第二个 yield 处
yield (y / 3) == 24/3 == 8;
【注意此时不用管函数表达式 let z = yield (y / 3),只用考虑yield (y / 3) 】
第三次 it.next(13) 会从上一次yield处开始let z = yield (y / 3);传入的参数等于 let z = yield (y / 3) =13;
综上所述 x=5;y=24;z=13;
同理,很容易实现8中迭代器的对象循环的方法:
let obj = {};
obj[Symbol.iterator] = function *(){
yield 1;
yield 3;
yield 5;
yield 7;
}
for(let key of obj){
console.log(key);
}
// 1 3 5 7
Generator 实现状态机,也就是固定的状态
let state=function* (){
while(1){
yield 'A';
yield 'B';
yield 'C';
}
}
let status=state();
console.log(status.next());//{value: "A", done: false}
console.log(status.next());//{value: "B", done: false}
console.log(status.next());//{value: "C", done: false}
console.log(status.next());//{value: "A", done: false}
console.log(status.next());//{value: "B", done: false}
注意 async...await 就是用的 Generator 的语法糖
应用一:抽奖功能,不需要全局定义剩余抽奖次数。
let draw=function(count){
//具体抽奖逻辑
console.info(`剩余${count}次`)
}
let residue=function* (count){
while (count>0) {
count--;
yield draw(count);
}
}
let star=residue(5);
let btn=document.createElement('button');
btn.id='start';
btn.textContent='抽奖';
document.body.appendChild(btn);
document.getElementById('start').addEventListener('click',function(){
star.next();
},false)
常规写法:
let draw=function(count){
//具体抽奖逻辑
console.info(`剩余${count}次`)
}
let residue=function (){
if (currNum>0) {
draw(currNum);
}
}
let currNum = 5;
let btn=document.createElement('button');
btn.id='start';
btn.textContent='抽奖';
document.body.appendChild(btn);
document.getElementById('start').addEventListener('click',function(){
residue();
currNum--;
},false)
常轮询:
let ajax=function* (){
yield new Promise(function(resolve,reject){
setTimeout(function () {
resolve({code:0})
}, 200);
})
}
let pull=function(){
let genertaor=ajax();
let step=genertaor.next();
step.value.then(function(d){
if(d.code!=0){
setTimeout(function () {
console.info('wait');
pull()
}, 1000);
}else{
console.info(d);
}
})
}
pull();
常规写法:
let ajax=function (){
return new Promise(function(resolve,reject){
setTimeout(function () {
resolve({code:10})
}, 200);
})
}
let pull=function(){
let genertaor=ajax();
genertaor.then(function(d){
if(d.code!=0){
setTimeout(function () {
console.info('wait');
pull()
}, 1000);
}else{
console.info(d);
}
})
}
pull();
11. reduce函数
reduce() 是数组的归并方法,与forEach()、map()、filter()等迭代方法一样都会对数组每一项进行遍历,但是reduce() 可同时将前面数组项遍历产生的结果与当前遍历项进行运算,
- 求数组项之和
let arr = ['1','2','3']
var sum = arr.reduce(function (prev, cur) {
return Number(prev) + Number(cur);
},0);
console.log(sum); //6
由于传入了初始值0,所以开始时prev的值为0,cur的值为数组第一项3,相加之后返回值为3作为下一轮回调的prev值,然后再继续与下一个数组项相加,以此类推,直至完成所有数组项的和并返回。
2. 求数组项最大值
let arr = ['12','2','5','8','4']
var max = arr.reduce(function (prev, cur) { //使用reduce将传入的数组分开
return Math.max(prev,cur);
});
console.log(max);
由于未传入初始值,所以开始时prev的值为数组第一项3,cur的值为数组第二项9,取两值最大值后继续进入下一轮回调。
方法2:
由于Math.max不能直接作用于数组,所以使用apply,第一个参数为null,表示window作用域。第二个参数是aa数组,会以单个
let aa = ['1','8','13','4','7']
console.log(Math.max.apply(null,aa))
类似的使用call
console.log(Math.max.call(null,'1','8','13','4','7'))
let bb = [1,8,13,4,7];
bb.sort((num1,num2)=>{
return num1-num2
})
bb.reverse()[0];//reverse 函数翻转数组
let bb = [1,8,13,4,7];
bb.sort((num1,num2)=>{
return num1-num2
})
console.log(bb);//[1, 4, 7, 8, 13]
- 数组去重
var newArr = arr.reduce(function (prev, cur) {
prev.indexOf(cur) === -1 && prev.push(cur);
return prev;
},[]);
//array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
//initialValue 可选。传递给函数的初始值
【参考文章】浅谈JS中 reduce() 的用法:https://www.jianshu.com/p/541b84c9df90
12. Array.from方法
Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。那么什么是类数组对象呢?所谓类数组对象,最基本的要求就是具有length属性的对象。
1、将类数组对象转换为真正数组.
let arrayLike = {
0: 'tom',
1: '65',
2: '男',
3: ['jane','john','Mary'],
'length': 4
}
let arr = Array.from(arrayLike)
console.log(arr) // ['tom','65','男',['jane','john','Mary']]
那么,如果将上面代码中length属性去掉呢?实践证明,答案会是一个长度为0的空数组。
这里将代码再改一下,就是具有length属性,但是对象的属性名不再是数字类型的,而是其他字符串型的,代码如下:
let arrayLike = {
'name': 'tom',
'age': '65',
'sex': '男',
'friends': ['jane','john','Mary'],
length: 4
}
let arr = Array.from(arrayLike)
console.log(arr) // [ undefined, undefined, undefined, undefined ]
会发现结果是长度为4,元素均为undefined的数组
由此可见,要将一个类数组对象转换为一个真正的数组,必须具备以下条件:
- 1、该类数组对象必须具有length属性,用于指定数组的长度。如果没有length属性,那么转换后的数组是一个空数组。
- 2、该类数组对象的属性名必须为数值型或字符串型的数字
ps: 该类数组对象的属性名可以加引号,也可以不加引号
Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。如下:
let arr = [12,45,97,9797,564,134,45642]
let set = new Set(arr)
console.log(Array.from(set, item => item + 1)) // [ 13, 46, 98, 9798, 565, 135, 45643 ]
3、将字符串转换为数组:
let str = 'hello world!';
console.log(Array.from(str)) // ["h", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d", "!"]
13. 字符串补全长度的功能。
如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。
let str = 'xx';
let bb = str.padStart(5,'0'); //第一个参数表示位数
console.log(bb); // 000xx
padEnd()常用来给小数点补充0:
let num1 = '34.1';
let num2 = num1.padEnd(5,'0');
console.log(num2); //34.10,注意包含了小数点占了一位