当前位置: 首页 > news >正文

案例展示在网站中的作用/网站优化包括哪些

案例展示在网站中的作用,网站优化包括哪些,网站建设社区,b2c网上购物平台已同步到个人博客,欢迎访问 简介 Generator函数有两个特征: function关键字后面有一个*函数体内部使用yield表达式 function* helloWorldGenerator() {yield hello;yield word;return ending } const hw helloWorldGenerator()调用函数后&#xff0…

已同步到个人博客,欢迎访问

简介

Generator函数有两个特征:

  1. function关键字后面有一个*
  2. 函数体内部使用yield表达式
function* helloWorldGenerator() {yield 'hello';yield 'word';return 'ending'
}
const hw = helloWorldGenerator()

调用函数后,函数并不执行,返回的也不是函数运行结果,而是指向内部状态的指针对象(即遍历器对象

然后必须调用遍历器对象的next方法,让指针移向下一个状态,直到遇到yidld(或者return)为止。也就是说,yiled是暂停执行的标记,而next方法可以恢复执行:

hw.next()
// { value: 'hello', done: false }hw.next()
// { value: 'world', done: false }hw.next()
// { value: 'ending', done: true }hw.next()
// { value: undefined, done: true }

每次调用nexd方法就会返回一个对象,对象有着valuedone两个属性,valueyield后面的值,done是一个布尔值,表示便利是否结束。

yiled表达式

yield是函数内部的暂停标志,执行逻辑:

(1)遇到yiled暂停执行,将其后面的值作为next返回对象的value属性值

(2)下一次调用next方法,继续执行,直到遇到下一个yield

(3)如果没有新的yield则运行到return或者函数运行结束

(4)将return的值作为返回对象的value属性值,如果没有return,返回对象的value属性值为undeinfed

yield提供了惰性求值的功能。

yield表达式只能用在Generator函数里面,用在其他地方都会报错

与Iterator接口的关系

可以将Generator函数赋值给对象的Symbol.iterator属性,从而使得对象具有Iterator接口

let obj = {};
[...obj]; // Uncaught TypeError: obj is not iterableobj[Symbol.iterator] = function* () {yield 1;yield 2;yield 3;
};[...obj] // [1, 2, 3]

next方法

yield表达式本身没有返回值或者说总是返回undefined(这个指的是它在内部对于本身的传递,而非传递给next返回对象的value的属性值)

function* f() {let a = yield 100;console.log(a, 'a');
}
let gen = f();gen.next();
// { value: 100, done: false }gen.next();
// undefined "a"
// { value: undefined, done: true }

a的值是undefined

next方法可以带一个参数,这个参数会被当做上一个yield的表达式的返回值。

function* f() {let a = yield 100;console.log(a, 'a');
}
let gen = f();gen.next();
// { value: 100, done: false }gen.next('hello');
// hello "a"
// { value: undefined, done: true }

这个功能,可以在Generator函数开始运行后,从外部向函数体内部注入值,从而调整函数行为。

注意,next注入的参数改变的是yield表达式的返回值

function* foo(x) {var y = 2 * (yield (x + 1));var z = yield (y / 3);return (x + y + z);
}var a = foo(5);
a.next() // { value:6, done:false }
a.next() // { value:NaN, done:false }
a.next() // { value:NaN, done:true }var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

当执行b.next(12)时,不是给y赋值12,而是yield (x + 1)12,所以value8

上面提到了,next的参数是赋值给上一个yield表达式返回值,所以在首次调用next传参是无效的。

第一次执行next方法,等同于启动执行Generator函数的内部代码

for...of循环

for...of循环可以自动遍历Generator生成的Iterator对象,不需要再逐步调用next方法

function* foo() {yield 1;yield 2;yield 3;yield 4;yield 5;return 6;
}for (let v of foo()) {console.log(v);
}
// 1 2 3 4 5

要注意的是,nextd方法的返回值的done属性为true时,for...of循环就会终止,并且不包括return的值

除了for...of之外,扩展运算符、解构赋值、Array.from内部都调用的遍历器接口,都可以将Generator函数返回的Iterator对象作为参数。

function* numbers () {yield 1yield 2return 3yield 4
}// 扩展运算符
[...numbers()] // [1, 2]// Array.from 方法
Array.from(numbers()) // [1, 2]// 解构赋值
let [x, y] = numbers();
x // 1
y // 2

Generator.prototype.throw()

Generator函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获

function* f() {try {yield 100;yield 200;} catch(e) {console.log('内部捕获', e)}
}
const gen = f();try {console.log(gen.next()); // { value: 100, done: false }gen.throw('a'); // '内部捕获, a'console.log(gen.next()); // { value: undefined, done: false }gen.throw('a'); // '外部捕获, b'
} catch (e) {console.log('外部捕获', e)
}

遍历器对象抛出的错误被Generator函数体内捕获后,Generator函数try语句内的其他语句就不再继续运行,并且遍历器对象抛出的其他错误也不会被Generator函数内捕获,而是被全局的catch捕获

try语句中抛出错误,try中的其他语句不会在继续执行

不要混淆遍历器对象的throw方法和全局的throw命令

如果函数内部没有部署try...catch代码,遍历器对象抛出的错误会被外部的额try...catch代码捕获

要注意的是,throw抛出的错误要被内部捕获,前提是必须执行过一次next方法,因为不执行一次next代码,意味着Generator函数没有启动执行,所以错误会被抛出在函数外部。

遍历器对象的throw方法被捕获后,自动执行了一次next方法,并且只要Generator函数内部部署了try...catch代码,throw方法也不会影响下一次遍历

function* f() {try {yield 100;yield 200;} catch(e) {console.log('内部捕获', e)}yield 300;
}const gen = f();console.log(gen.next()); // { value: 100, done: false }console.log(gen.throw('a')); // '内部捕获, a', {value: 300, done: false}

这种在函数体内捕获错误的机制,大大方便了错误的处理,多个yield表达式,可以在函数内部使用一个try...catch代码块来捕获错误就可以了。

Generator函数内部的错误,也可以被函数体外的catch捕获,但是函数内部的代码就不会再继续执行了,JavaScript认为这个Generator已经结束运行了,再调用next方法会返回一个value属性为undefineddone属性为true的对象

Generator.prototype.return()

Generator函数返回的遍历器对象有return方法,可以返回给定的值,提前结束Generator函数。

function* f() {yield 100;yield 200;return 300;
}const g = f();g.next()
// { value: 100, done: false }g.return(800)
// { value: 800, done: true }g.next()
// { value: undefined, done: true }

return不提供参数,返回值的value属性是undefined

如果Generator内部有try...finally代码块,且正在执行try代码,return方法会推迟到finally代码块执行完再执行

nextthrowreturn的共同点

三者都是让Generator函数恢复执行,并且使用不同的语句替换yield表达式

next是将yield表达式替换为一个值,throw是将yield表达式替换成throw语句,return是将yield表达式替换为return语句

const g = function* (x, y) {let result = yield x + y;return result;
};const gen = g(1, 2);
gen.next(); // Object { value: 3, done: false }

next

gen.next(1); // Object { value: 1, done: true }
// 相当于将 let result = yield x + y
// 替换成 let result = 1;

throw

gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));

return

gen.return(2); // Object { value: 2, done: true }
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;

yield*表达式

如果在Generator函数内部调用另外一个Generator函数,默认情况下是无效的

function* foo() {yield 100;yield 200;
}function* bar() {yield 300;foo();yield 400;
}for(let i of bar()) {console.log(i)
}
// 300 400

在Generator函数内部调用另外一个Generator函数需要用到yield*表达式

function* foo() {yield 100;yield 200;
}function* bar() {yield 300;yield* foo();// 相当于// yield 100;// yield 200;// 等同于// for(let v of foo()) {//   yield v// }yield 400;
}for(let i of bar()) {console.log(i)
}
// 300 100 200 400

yield*后面的Generator函数(没有return语句时)等同于在Generator函数内部部署一个for...of循环

function* concat(iter1, iter2) {yield* iter1;yield* iter2;
}// 等同于function* concat(iter1, iter2) {for (var value of iter1) {yield value;}for (var value of iter2) {yield value;}
}

return语句时,可以通过赋值var value = yield* iterator获取return语句的值,yield*后面表达式中的return语句作为一个遍历的结果,而不是作为yield*的的返回值

function* foo() {return 1;
}function* bar() {const x = yield* foo();return x;
}const gen = bar();
gen.next();
// { value: 1, done: true }

如果yiled*后面跟着一个数组,会直接遍历这个数组:

function* foo() {yield 300;yield [1, 2, 3];yield 400;
}for(let i of bar()) {console.log(i)
}
// 300 [1, 2, 3], 400function* bar() {yield 300;yield* [1, 2, 3];yield 400;
}for(let i of bar()) {console.log(i)
}
// 300 1, 2, 3, 400

实际上,任何数据结构只要有Iterator接口,就可以被yield*遍历

const read = (function* () {yield 'hello';yield* 'hello'
})();
read.next().value; // 'hello'
read.next().value; // 'h'

作为对象属性的Generator函数

可以简写为下面的形式:

let obj = {* myGeneratorMethod() {···}
};

Generator函数的this

Generator函数总是返回一个遍历器,ES6规定这个遍历器是Generator函数的实例:

function* gen(){}
gen.prototype.say = function() {console.log('hello')
}let obj = gen();obj instanceof gen; // true
obj.say(); // 'hello'

但是Generator不能作为构造函数使用,因为它的返回值总是一个遍历器对象,而非this对象(即使显示声明return this也不可以)

function* gen(){this.test = 123;
}let obj = gen();gen.test; // undefined

Generator函数也不能和new一起使用,会报错。

Generator与协程

协程有多个线程(函数),可以并行执行,但是只有一个线程(函数)处在正在运行的状态,其他线程(函数)都处于暂停状态(suspended),线程(函数)之间可以交换控制权。

协程以多占用内存为代价,实现多任务的并行。

Generator函数是ES6对协程的不完全实现,成为“半协程”,只有Generator函数的调用者才有权将程序的执行权还给Generator函数(完全协程,任何函数都可以将暂停的协程继续执行)

如果将Generator函数当作协程,可以将多个需要相互协作的任务写作Generator函数,之间使用yield表达式交换控制权

练习 & 应用

判断Generator函数输出结果1

function* dataConsumer() {console.log('Started');console.log(`1. ${yield}`);console.log(`2. ${yield}`);return 'result';
}let genObj = dataConsumer();console.log(genObj.next());console.log(genObj.next('a'))console.log(genObj.next('b'))

思路:next的参数是对上一次的yield表达式返回值赋值,所以拆开来看:

function* dataConsumer() {console.log('Started');console.log(`1. ${yield}`); // -- genObj.next() {value: undefined, done: false },此时第二个console.log被暂停了console.log(`2. ${yield}`); // -- genObj.next('a') {value: undefined, done: false } 第二个console.log执行,传入了areturn 'result'; // -- genObj.next('b') {value: 'result', done: true },最后一个console.log执行,传入了b
}

判断Generator函数输出结果2

function* g() {yield 1;console.log('throwing an exception');throw new Error('generator broke!');yield 2;yield 3;
}function log(generator) {var v;console.log('starting generator');try {v = generator.next();console.log('第一次运行next方法', v);} catch (err) {console.log('捕捉错误', v);}try {v = generator.next();console.log('第二次运行next方法', v);} catch (err) {console.log('捕捉错误', v);}try {v = generator.next();console.log('第三次运行next方法', v);} catch (err) {console.log('捕捉错误', v);}console.log('caller done');
}log(g());

Generator函数内部的错误,如果没有被内部捕获,则会被外部捕获,这时候Generator函数执行完毕,不再继续执行

注意,在内部抛出错误后,next返回值仍为上一次的返回值,并非抛出的结果。

结果:

// starting generator
// 第一次运行next方法 { value: 1, done: false }
// throwing an exception
// 捕捉错误 { value: 1, done: false }
// 第三次运行next方法 { value: undefined, done: true }
// caller done

判断Generator函数输出结果3

function* foo() {yield 2;yield 3;return "foo";
}function* bar() {yield 1;var v = yield* foo();console.log("v: " + v);yield 4;
}var it = bar();console.log(it.next());console.log(it.next());console.log(it.next());console.log(it.next());console.log(it.next());

要注意的是,被代理的Generator函数的return语句,不再作为next方法的输出结果,而是用来向代理它的Generator函数返回数据

// { value: 1, done: false }// { value: 2, done: false }// { value: 3, done: false }// v: foo
// { value: 4, done: false }// { value: undefined, done: true }

判断Generator函数输出结果4

function* genFuncWithReturn() {yield 'a';yield 'b';return 'The result';
}
function* logReturned(genObj) {let result = yield* genObj;console.log(result);
}console.log([...logReturned(genFuncWithReturn())])

需要好好判断顺序,首先genFuncWithReturn()返回了一个迭代器对象,然后传入了logReturned中,按顺序执行,执行了console.log(result)之后,才会执行解构操作,所以顺序是:

// The result
// ["a", "b"]

如果换一种形式输出结果就不同了:

for (let i of logReturned(genFuncWithReturn())) {console.log(i)
}
// a
// b
// The result

关键点就是解构操作符是等到函数执行后再执行的。

for...of可以遍历原生对象

原生的JavaScript对象时候不能使用for...of进行遍历的,因为并没有部署遍历接口。

let obj = {name: 'jay',age: 31
}for(let i of obj) {console.log(i)
}
// Uncaught TypeError: obj is not iterable

使用Generator函数,for...of可以遍历原生对象。

思路:由于原生对象没有部署遍历器接口,所以需要为对象的遍历器接口部署一个Generator函数,返回一个遍历器对象

obj[Symbol.iterator] = function* () {let keys = Object.getOwnPropertyNames(this);for(let key of keys) {yield [key, this[key]]}
};

可以编写一个更通用的方法:

function makeIterator (obj) {let keys = Object.getOwnPropertyNames(obj);obj[Symbol.iterator] = function* () {for(let key of keys) {yield [key, obj[key]]}}
}

第一次调用next方法就能够传值

next的参数是赋值给上一个yield表达式返回值,所以在首次调用next传参是无效的。

构造一个wrapper函数,返回一个Generator函数,实现在第一次调用next方法时就能够输入值。

const wrapped = wrapper(function* () {console.log(`First input: ${yield}`);return 'DONE';
});wrapped().next('hello!')
// First input: hello!

思路:既然Generator首个next不能传参,那么就在我们的包裹函数中,将首次next调用在包裹函数内执行

const wrapper = function(fn) {return function(...args) {const gnObject = fn(...args);gnObject.next();return gnObject}
}

利用Generator函数和for...of循环,实现斐波那契数列

function* fibonacci() {let [prev, curr] = [0, 1];for(;;) {yield curr;[prev, curr] = [curr, curr + prev]}
}for(let v of fibonacci()) {if(v > 100) {break}console.log(v)
}

实现一个clock状态机

如果不使用Generator函数:

const clock = (function () {let ticking = true;return function () {console.log(ticking ? 'tick' : 'tock');ticking = !ticking}
})()

使用Generator函数:

const clock = (function* () {for(;;) {console.log('tick');yield;console.log('tock');yield}
})()

输出多维数组中的值

const numbers = flatten2([1, [[2], 3, 4], 5])
numbers.next().value // => 1
numbers.next().value // => 2
numbers.next().value // => 3
numbers.next().value // => 4
numbers.next().value // => 5

思路就是递归调用Generator函数:

function* flatten(arr) {for(let i of arr) {if (Array.isArray(i) {yield* flatten(i)} else {yield i}}
}

要注意的就是,在一个Generator函数里面调用另外一个Generator函数,默认是无效的,所以必须使用yield*表达式来调用

遍历二叉树

对二叉树这里有些迷糊,因为基础不牢固,回头好好不玩了数据结构和算法,再来重新看一下这里(2019.01.17)

// 下面是二叉树的构造函数,
// 三个参数分别是左树、当前节点和右树
function Tree(left, label, right) {this.left = left;this.label = label;this.right = right;
}// 下面是中序(inorder)遍历函数。
// 由于返回的是一个遍历器,所以要用generator函数。
// 函数体内采用递归算法,所以左树和右树要用yield*遍历
function* inorder(t) {if (t) {yield* inorder(t.left);yield t.label;yield* inorder(t.right);}
}// 下面生成二叉树
function make(array) {// 判断是否为叶节点if (array.length == 1) return new Tree(null, array[0], null);return new Tree(make(array[0]), array[1], make(array[2]));
}
let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);// 遍历二叉树
var result = [];
for (let node of inorder(tree)) {result.push(node);
}result
// ['a', 'b', 'c', 'd', 'e', 'f', 'g']

控制流管理

如果一个多步操作非常耗时,采用回调函数,可能会写成下面这样:

step1(function (value1) {step2(value1, function(value2) {step3(value2, function(value3) {step4(value3, function(value4) {// Do something with value4});});});
});

改写成Promise格式:

Promise.resolve(step1).then(step2).then(step3).then(step4).then(value4 => {// Do something with value4}, error => {// catch the error from step1 through step4})

改写成Generator格式:

function* gen(value1) {try {const value2 = yield step1(value1);const value3 = yield step2(value2);const value4 = yield step3(value3);const value5 = yield step4(value4);// Do something with value4} catch(e) {// catch the error from step1 through step4}
}

需要一个函数按次序调用:

scheduler(longRunningTask(initialValue));function scheduler (task) {const taskObj = task.next(task.value);// 如果Generator函数未结束,就继续调用if(!taskObj.done) {task.value = taskObj.value;scheduler(task)}
}

上面这种做法,只适合同步操作,即所有的task都必须是同步的,不能有异步操作。

让Generator函数能够使用new

Generator函数不能和new命令一起用,会报错:

function* F() {yield this.x = 2;yield this.y = 3;
}new F()
// TypeError: F is not a constructor

如何让Generator函数返回一个正常的对象实例,既可以用next方法,又可以获得正常的this

function* gen () {this.a = 1;yield this.b = 2;yield this.c = 3;
}function F (){// do something here
}let f = new F();f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}f.a // 1
f.b // 2
f.c // 3

实现:

function F (){return gen.call(gen.prototype)
}

实际上这是一个有欺骗性的做法,实际上new关键字无效的,我们要的只是执行F即可。

let f = new F();

gen.call(gen.prototype)相当于在gen原型上添加了属性,当访问f.a时实际上访问的就是原型链上的属性。

参考

  • Generator 函数的语法@ECMAScript6入门
http://www.lbrq.cn/news/1266301.html

相关文章:

  • 个人博客手机网站模板/自己创建网页
  • 做网站主要显哪些内容/营销软件网站
  • 网站开发项目总结报告/手机推广软文
  • 网站开发成功案例/品牌传播策略
  • 网站开发算法/百度软文
  • 有空间与域名后怎么做网站/seo都用在哪些网站
  • 北京微信小程序/seo数据统计分析工具有哪些
  • 网站建设有什么作用/重庆seo黄智
  • 网站建设服务器费用/360网站推广官网
  • 网站登记备案 个人/国内企业网站模板
  • 什么网站可以免费做视频的软件有哪些/seo工作前景如何
  • wordpress仿论坛/汕头seo排名公司
  • 论述网站建设的步骤/网站策划运营
  • 阿里云里做网站能上百度首页么/百度怎样发布作品
  • 文章采集网站/电商线上推广
  • 制作网站要花多少钱如何/今日国内新闻大事
  • 吉林企业网站建设/国内搜索引擎排名第一
  • 建设网站都要什么/如何策划一个营销方案
  • 企业网络营销的优势/太原百度seo排名软件
  • 萝岗手机网站建设/建网站免费
  • 广州哪里做公司网站号/seo网站优化技术
  • 洞口县建设局网站/市场营销的对象有哪些
  • 本地做网站/常用的网络营销方法有哪些
  • 如何设置一个网站/网络营销的概念是什么
  • 烟台做网站哪里好/营销平台有哪些
  • 宁波制作网站知名/企业如何进行品牌推广
  • 网站建设liluokj/百度优化软件
  • 短网址网站建设/seo分析及优化建议
  • 平阴网站建设/关键词排名优化技巧
  • 网站统计怎么做/百度一下你就知道官方
  • nestjs @Param 从入门到精通
  • 知识蒸馏 - 基于KL散度的知识蒸馏 HelloWorld 示例 采用PyTorch 内置函数F.kl_div的实现方式
  • 本地环境vue与springboot联调
  • i Battery Box V3.7 客户端电池检测仪
  • Android13文件管理USB音乐无专辑图片显示的是同目录其他图片
  • 除数博弈(动态规划)