ES6 Generator函数 语法 深入理解

本文深入探讨ES6中的Generator函数,详细讲解GeneratorFunction类和Generator类,以及如何调用和使用Generator函数。通过next、throw和return方法恢复执行,分析yield、throw、return关键字在函数体中的作用,展示其在异步操作、控制流管理和部署Iterable接口的应用。
摘要由CSDN通过智能技术生成

前提

ES6中的Generator函数,这个函数与两个内部原生类有关,分别是GeneratorFunction类Generator类,这个两个类是内部原生类我们只能在谷歌浏览器的控制台上能看见,但是我们无法直接使用这两个类。

GeneratorFunction类

使用function关键字定义的普通函数,其本质是一个Function类的实例对象:

// 使用 function 关键字定义了一个普通函数
function normalFunction(){
   
	var variable = 1;
	console.log(variable);
}
// normalFunction 的原型对象就是 Function类 的 prototype 属性的值
Object.getPrototypeOf(normalFunction) === Function.prototype // true

使用 function关键字 和 *符号 定义的Generator函数,其本质是一个GeneratorFunction类的实例对象,GeneratorFunction类在代码中无法使用,暂时只能通过浏览器的控制台显示而知道它的存在:

function* generatorFunction(){
   
	yield 1;
	return 2;
}
// 获取 Generator函数 的原型对象
// GeneratorFunction {prototype: Generator, constructor: ƒ, Symbol(Symbol.toStringTag): "GeneratorFunction"}
var prototype = Object.getPrototypeOf(generatorFunction);
// 获取 Generator函数 的 构造函数对象
// ƒ GeneratorFunction() { [native code] }
var constructor = Object.getPrototypeOf(generatorFunction).constructor; 

// Generator函数 的构造函数对象上的prototype属性 === Generator函数 的原型对象
constructor.prototype === prototype; // true

GeneratorFunction类Function类的子类,所以Generator函数本质依然是一个函数对象:

// GeneratorFunction类 的原型对象的原型对象是 Function类 的原型对象
Object.getPrototypeOf(Object.getPrototypeOf(generatorFunction)) === Function.prototype; // true
// GeneratorFunction类 的构造函数对象的原型对象是 Function类 的构造函数对象
Object.getPrototypeOf(Object.getPrototypeOf(generatorFunction).constructor) === Function; // true

既然Generator函数本质上也是一个函数对象,那么Generator函数对象和普通函数对象在属性上其实大部分是一致的,但其中有一个属性是有很大的区别的:prototype属性。

  • 普通函数对象C在新建完成后,会自动以Object.prototype作为原型对象创建一个新对象N,然后在对象N上添加一个constructor属性,属性的值就是对象C,最后将对象N作为对象C的prototype属性的值。

    Reflect.ownKeys(normalFunction); // ["length", "name", ... "prototype"]
    
    // 普通函数 对象的原型对象上是没有 prototype 属性的,所以 普通函数 对象的上 prototype 属性不是来自继承
    Object.getPrototypeOf(normalFunction).prototype; // undefined
    
    Object.getPrototypeOf(normalFunction.prototype) === Object.prototype; // true
    normalFunction.prototype.constructor === normalFunction; // true
    
  • GeneratorFunction类原型对象上是存在prototype属性的,按照继承原型链的规则,那么Generator函数对象上的prototype属性应该是继承自原型对象的,但是实际上不是,Generator函数实例对象在新建完成后,会自动以GeneratorFunction类原型对象上的prototype属性的值作为原型对象创建一个新对象G,然后将对象G作为Generator函数对象的prototype属性的值,但是注意这里不会和普通函数一样给prototype属性的值的constructor属性进行重新赋值。

    Reflect.ownKeys(generatorFunction); // ["length", "name", "prototype"]
    
    // Generator {constructor: GeneratorFunction, next: ƒ, return: ƒ, throw: ƒ, Symbol(Symbol.toStringTag): "Generator"}
    Object.getPrototypeOf(generatorFunction).prototype; 
    generatorFunction.prototype === Object.getPrototypeOf(generatorFunction).prototype; // false
    
    Object.getPrototypeOf(generatorFunction.prototype) === Object.getPrototypeOf(generatorFunction).prototype; // true
    

GeneratorFunction类的原型对象 是所有的Generator函数实例对象的原型对象,但是其不止这一个身份,其也是Generator类的构造函数对象,只是这个构造函数对象极其特殊。

总结:GeneratorFunction类Function类的子类,Generator函数GeneratorFunction类的实例对象,GeneratorFunction类是定义声明Generator函数的模版。

Generator类

JavaScript中我们想要定义一个类的时候,首先需要一个函数对象,其次这个函数对象上必须有一个prototype属性,而prototype属性的值是一个对象,这个对象上有一个constructor属性,而constructor属性的值指向前面的函数对象。那么我们可以以函数对象的函数名为类名,函数对象是这个类的构造函数,函数对象上的prototype属性的值则是类的原型对象。

按照上文的描述,我们可以把 GeneratorFunction类的原型对象 认为是一个类的构造函数对象。

// 获取 GeneratorFunction类 的原型对象
var generatorFunctionPrototype = Object.getPrototypeOf(function*(){
   });

// generatorFunctionPrototype 的 prototype 属性的值是一个对象,下面是谷歌浏览器控制台上的显示
// Generator {constructor: GeneratorFunction, next: ƒ, return: ƒ, throw: ƒ, Symbol(Symbol.toStringTag): "Generator"}
generatorFunctionPrototype.prototype;

// generatorFunctionPrototype 的 prototype 属性值的 constructor 属性的值等于 generatorFunctionPrototype
generatorFunctionPrototype.prototype.constructor === generatorFunctionPrototype; // true

// 而且 generatorFunctionPrototype 对象其实是与普通函数平级的,有着同一个原型对象
// 所以我们可以将 generatorFunctionPrototype 对象当作一个特殊类的特殊构造函数对象。 
Object.getPrototypeOf(generatorFunctionPrototype) === Object.getPrototypeOf(function(){
   }); // true

按照谷歌浏览器控制台上的显示我们将这个特殊内部类称为Generator类,如此上面代码中的generatorFunctionPrototype就是Generator类的构造函数对象。

Generator函数则可以认为是Generator类子类的构造函数对象

function* generatorFunction(){
   }
// Generator函数 的原型对象是 generatorFunctionPrototype
Object.getPrototypeOf(generatorFunction) === generatorFunctionPrototype;
// Generator函数 的 prototype 属性的值的原型对象是 generatorFunctionPrototype 的 prototype 属性的值
Object.getPrototypeOf(generatorFunction.prototype) === generatorFunctionPrototype.prototype;

我们此时完全可以将Generator函数的函数名作为一个类的类名,且这个类是Generator类的子类,但是这个类有一个缺陷,Generator函数prototype属性值的constructor属性的值并不是指向Generator函数,而是从原型对象上继承来的:

generatorFunction.prototype.constructor === generatorFunction; // false
generatorFunction.prototype.constructor === generatorConstructor // true

所以我们本文中将Generator函数作为Generator类的次级构造函数对象,GeneratorFunction类的原型对象作为Generator类的初始构造函数对象,Generator函数prototype属性值作为Generator类的次级原型对象,GeneratorFunction类的原型对象的prototype属性值作为Generator类的初始原型对象。

猜测:之所以有次级与初始的分别,估计是为了Generator类实例对象的归属进行区分,因为Generator类实例对象是以次级原型对象进行创建的,而次级原型对象又是以初始原型对象创建的,那么每定义一个Generator函数就会出现一个新的次级原型对象,同时每调用一次Generator函数就会返回一个属于次级原型对象的Generator类实例对象。如此初始原型对象只会有一个,而次级原型对象会有多个,如果想再所有Generator类实例对象上添加新属性,那直接在初始原型对象上添加就行,而如果只是想在某个Generator函数返回的Generator类实例对象上添加属性,则在次级原型对象上添加就行。

一、Generator函数 调用

函数调用大致可以分为两种,一种通过new关键字调用,另一种被某个对象调用。
普通函数两种调用方法都可以使用,而Generator函数只能使用被某个对象调用这个方法:

function normalFunction(){
   
	var variable = 1;
	console.log(variable);
}
function* generatorFunction(){
   
	yield 1;
	return 2;
}
// 被全局对象window调用
normalFunction(); // 运行正常
// 被{}对象调用
normalFunction.call({
   a:1});
new normalFunction(); // 运行正常
// 被全局对象window调用
generatorFunction();	// 运行正常
// 被{}对象调用
generatorFunction.call({
   b:2});
new generatorFunction(); // 报错

普通函数Generator函数在调用时的区别:

  • 普通函数在被某个对象调用时,是直接执行函数体中的代码。而通过new关键字调用则会先以函数实例对象的prototype属性的值作为原型对象创建一个对象N,然后在用对象N调用这个函数,对对象N进行初始化,此时我们可以认为函数名是一个类名,对象N是这个类的实例对象,最后将对象N作为函数的返回值。

  • Generator函数在被某个对象W调用时,则不在是直接执行Generator函数的函数体中的代码了,而是先以Generator函数函数实例对象的prototype属性的值作为原型对象创建一个对象G(这个对象就是Generator类实例对象),然后才会使用对象W(注意不是新建的Generator类实例对象去调用函数)调用Generator函数去执行函数体代码,但是这里的调用不是平常的函数调用,JS解释器从Generator函数实例对象的内存中取出字符串形式的函数体后,解析字符串形式的函数体,解析成功会产生一个属于Generator函数上下文执行环境,但是不会将这个新生上下文执行环境加入上下文执行环境栈(压栈)中从而执行函数体代码(此时产生了函数暂停执行的效果),而是将这个上下文执行环境挂靠在对象G上,然后将对象G作为Generator函数的返回值。

    function* He(){
         
    	console.log('He');
    }
    He(); 
    // He()调用的返回值
    // He { // 谷歌浏览器控制台上的显示
    //	[[GeneratorStatus]]: "suspended",
    // 	[[GeneratorFunction]]: ƒ* He(),
    // 	[[GeneratorReceiver]]: Window
    // }
    

Generator函数被某个对象调用后,返回的是一个Generator类实例对象,这个实例对象通过浏览器控制台的显示可以大致将其包含的信息分为三个部分:

  • [[GeneratorStatus]]:这个内部属性的值是Generator函数的执行状态,其有三个值,分别是suspended(属于函数的上下文执行环境不在上下文执行环境栈中,而是移出执行栈中并被挂靠冻结了,代表着函数体暂停执行)、running(属于函数的上下文执行环境重新加入到上下文执行环境栈并执行,代表着函数体正在执行中)、closed(函数执行结束,无法在使用Generator类实例对象去重新执行函数)
  • [[GeneratorFunction]]:这个内部属性的值指向了被调用的Generator函数
  • [[GeneratorReceiver]]:这个内部属性的值指向了调用Generator函数的某个对象
function* generator(){
   
	// 此处打了个断点,可以清楚知道g1这个 Generator类 实例对象上挂靠的 Generator函数 此时的状态
	console.log(g1); // {[[GeneratorStatus]]: "running", [[GeneratorFunction]]: ƒ* generator(), [[GeneratorReceiver]]: obj{a:1,b:2,c:3},}
	debugger;
	yield 1;
	console.log('Generator');
	yield 2;
}
var obj = {
   a:1,b:2,c:3};
var g1 = generator.call(obj);
// g1是一个 Generator类 的实例对象,这个对象上挂靠了一个处于暂停执行状态下的 Gnerator函数
g1; // {[[GeneratorStatus]]: "suspended", [[GeneratorFunction]]: ƒ* generator(), [[GeneratorReceiver]]: obj{a:1,b:2,c:3},}
// 当调用了 Generator类 的实例对象的next方法后,挂靠在对象上的处于暂停执行状态下的 Gnerator函数会重新执行
g1.next(); // {value: 1, done: false}
g1.next(); // {value: 2, done: false}
g1.next(); // {value: undefined, done: true}
// 当 Generator函数 中的函数体代码都执行完成后,在此查看挂靠在g1对象上的 Generator函数 的状态已经是执行结束了
g1; // {[[GeneratorStatus]]: "closed", [[GeneratorFunction]]: ƒ* generator(), [[GeneratorReceiver]]: obj{a
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值