1. 箭头函数的特性

箭头函数表达式的语法比函数表达式更短,并且不绑定自己的this,arguments,super或 new.target。这些函数表达式最适合用于非方法函数(non-method functions),并且它们不能用作构造函数。
那么什么是 non-method functions 呢?

  1. 没有 this
  2. 没有 arguments: 但是可以访问外部环境的arguments
  3. 不能通过 new 关键字调用:
    1. JavaScript 函数有两个内部方法:[[Call]] 和 [[Construct]]。
    2. 箭头函数并没有 [[Construct]] 方法,不能被用作构造函数,如果通过 new 的方式调用,会报错。
  4. 没有 new.target
  5. 没有原型
  6. 没有 super: 连原型都没有,自然也不能通过 super 来访问原型的属性,所以箭头函数也是没有 super 的,不过跟 this、arguments、new.target 一样,这些值由外围最近一层非箭头函数决定。
  1. var obj = {
  2. i: 10,
  3. b: () => console.log(this.i, this),
  4. c: function() {
  5. console.log( this.i, this)
  6. }
  7. }
  8. obj.b();
  9. // undefined Window
  10. obj.c();
  11. // 10, Object {...}

2. for …of (ES6迭代器)

所谓迭代器,其实就是一个具有 next() 方法的对象,每次调用 next() 都会返回一个结果对象,该结果对象有两个属性,value 表示当前的值,done 表示遍历是否结束。

  1. E5写一下迭代器原理
  2. function createIterator(item){
  3. var i = 0 ;
  4. return {
  5. next: function() {
  6. var done = i >= item.length; //i如果为最后一个的话
  7. var value = !done ? items[i++] : undefined; //value就是undefined
  8. return {
  9. done: done,
  10. value: value
  11. };
  12. }
  13. };
  14. }
  15. // iterator 就是一个迭代器对象
  16. var iterator = createIterator([1, 2, 3]);
  17. console.log(iterator.next()); // { done: false, value: 1 }
  18. console.log(iterator.next()); // { done: false, value: 2 }
  19. console.log(iterator.next()); // { done: false, value: 3 }
  20. console.log(iterator.next()); // { done: true, value: undefined }

ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是”可遍历的”(iterable)。

  1. //for..of测试(这个是针对对象)
  2. const forOF = ()=>{
  3. const obj={
  4. value: 1
  5. }
  6. for(let value of obj){
  7. console.log("value",value);
  8. }
  9. }
  10. forOF(); //报错obj[Symbol.iterator] is not a function; 可以得知这里遍历的是[Symbol.iterator]
  11. //争对数组测试
  12. const obj= ["red", "green", "blue"];
  13. for(let value of obj){
  14. console.log("value",value); //得到正确答案
  15. }

数组遍历尽管我们没有手动添加 Symbol.iterator 属性,还是可以遍历成功,这是因为 ES6 默认部署了 Symbol.iterator 属性,当然我们也可以手动修改这个属性:

  1. var colors = ["red", "green", "blue"];
  2. colors[Symbol.iterator] = function() {
  3. return createIterator([1, 2, 3]);
  4. };
  5. for (let color of colors) {
  6. console.log(color);
  7. }
  8. // 1
  9. // 2
  10. // 3

除开数组之外,还有其他的类项也默认带了Symbol.iterator

  1. 数组
  2. Set
  3. Map
  4. 类数组对象, 如arguments对象,DOM NodeList等
  5. Generator 对象
  6. 字符串

    模拟forOf循环

  1. function forOf(obj, cb){
  2. let iterable, result;
  3. if(typeof obj[Symbol.iterator] != 'function')throw new TypeError(result + " is not iterable");
  4. if(typeof cb !== 'function') throw new TypeError("cb must be callable");
  5. iterable = obj[Symbol.iterator]();
  6. result = iterable.next(); //next执行的就是默认迭代器,如果不存在的话就会返回undefined
  7. while (!result.done) {
  8. cb(result.value);
  9. result = iterable.next(); //每一步去执行,则result就会被重新赋值,
  10. }
  11. }
  12. let data = forOf([1,2,3,4], (val)=>{console.log("val", val)})

内建迭代器

为了更好的访问对象中的内容,比如有的时候我们仅需要数组中的值,但有的时候不仅需要使用值还需要使用索引,ES6 为数组、Map、Set 集合内建了以下三种迭代器:

  1. entries() 返回一个遍历器对象,用来遍历[键名, 键值]组成的数组。对于数组,键名就是索引值。
  2. keys() 返回一个遍历器对象,用来遍历所有的键名。
  3. values() 返回一个遍历器对象,用来遍历所有的键值。
  1. let data = ["data1","data2","data3"];
  2. let keys = data.keys(); //Array Iterator;0,1,2
  3. let values = data.values(); //"data1","data2","data3",
  4. let entries = data.entries(); [0,'data1'],[1,'data2'],[2,'data3'];
  5. for (let index of keys) {
  6. console.log(index);
  7. }
  8. // 0
  9. // 1
  10. // 2
  11. for (let color of values) {
  12. console.log(color);
  13. }
  14. // red
  15. // green
  16. // blue
  17. for (let item of entries) {
  18. console.log(item);
  19. }
  20. // [ 0, "red" ]
  21. // [ 1, "green" ]
  22. // [ 2, "blue" ]
  23. /**
  24. *Set对象
  25. */
  26. var colors = new Set(["red", "green", "blue"]);
  27. 1) for (let index of colors.keys()) {
  28. console.log(index);
  29. }
  30. // red
  31. // green
  32. // blue
  33. 2) for (let color of colors.values()) {
  34. console.log(color);
  35. }
  36. // red
  37. // green
  38. // blue
  39. 3) for (let item of colors.entries()) {
  40. console.log(item);
  41. }
  42. // [ "red", "red" ]
  43. // [ "green", "green" ]
  44. // [ "blue", "blue" ]
  45. /**
  46. * Set, Map对象的区别
  47. */
  48. const values1 = new Set([1, 2, 3]);
  49. for (let value of values1) {
  50. console.log(value);
  51. }
  52. // 1
  53. // 2
  54. // 3
  55. const values2 = new Map([["key1", "value1"], ["key2", "value2"]]);
  56. for (let value of values2) {
  57. console.log(value);
  58. }
  59. // ["key1", "value1"]
  60. // ["key2", "value2"]
  61. ////////////////遍历 Map 数据结构的时候可以顺便结合解构赋值://////////////
  62. const valuess = new Map([["key1", "value1"], ["key2", "value2"]]);
  63. for (let [key, value] of valuess) {
  64. console.log(key + ":" + value); //设置属性
  65. }
  66. // key1:value1
  67. // key2:value2

Set的特点:
Set 类型的 keys() 和 values() 返回的是相同的迭代器,这也意味着在 Set 这种数据结构中键名与键值相同。
而且每个集合类型都有一个默认的迭代器,在 for-of 循环中,如果没有显式指定则使用默认的迭代器。数组和 Set 集合的默认迭代器是 values() 方法,Map 集合的默认迭代器是 entries() 方法。
这也就是为什么直接 for of 遍历 Set 和 Map 数据结构,会有不同的数据结构返回。

Babel 是如何编译 for of 的

我们可以在 Babel 的 Try it out 中查看编译的结果:

  1. const colors = new Set(["red", "green", "blue"]);
  2. for (let color of colors) {
  3. console.log(color);
  4. }
  5. Babel编译
  6. //而这段编译的代码稍微复杂的地方有两段,一段是 for 循环这里:
  7. "use strict"; //严格模式下
  8. var colors = new Set(["red", "green", "blue"]);
  9. var _iteratorNormalCompletion = true;
  10. var _didIteratorError = false;
  11. var _iteratorError = undefined;
  12. try {
  13. 1 复杂的地方1
  14. for (var _iterator = colors[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
  15. var color = _step.value;
  16. console.log(color);
  17. }
  18. } catch (err) {
  19. _didIteratorError = true;
  20. _iteratorError = err;
  21. } finally {
  22. try {
  23. if (!_iteratorNormalCompletion && _iterator.return != null) {
  24. _iterator.return();
  25. }
  26. } finally {
  27. if (_didIteratorError) {
  28. throw _iteratorError;
  29. }
  30. }
  31. }

至少由编译的结果可以看出,使用 for of 循环的背后,还是会使用 Symbol.iterator 接口。

遍历器对象除了具有 next 方法,还可以具有 return 方法和 throw 方法。如果你自己写遍历器对象生成函数,那么 next 方法是必须部署的,return 方法和 throw 方法是否部署是可选的。

return 方法的使用场合是,如果 for…of 循环提前退出(通常是因为出错,或者有 break 语句或 continue 语句),就会调用 return 方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署 return 方法。

3.Promise

promise解决了什么问题?

1.嵌套问题

  1. request(url, function(err, res, body) {
  2. if (err) handleError(err);
  3. fs.writeFile('1.txt', body, function(err) {
  4. request(url2, function(err, res, body) {
  5. if (err) handleError(err)
  6. })
  7. })
  8. });
  9. 使用 Promise 后:
  10. request(url)
  11. .then(function(result) {
  12. return writeFileAsynv('1.txt', result)
  13. })
  14. .then(function(result) {
  15. return request(url2)
  16. })
  17. .catch(function(e){
  18. handleError(e)
  19. });
  1. 红绿灯实例

    1. 题目:红灯三秒亮一次,绿灯一秒亮一次,黄灯2秒亮一次;如何让三个灯不断交替重复亮灯?(用 Promse 实现)
  1. function red(){
  2. console.log('red');
  3. }
  4. function green(){
  5. console.log('green');
  6. }
  7. function yellow(){
  8. console.log('yellow');
  9. }
  10. 利用 then 和递归实现:
  11. var light = function (timer, cb){
  12. return Promise(function(resolve, reject){
  13. setTimeout(()=>{
  14. cb(); //回调
  15. resolve(); //结束
  16. },timer)
  17. })
  18. }
  19. var step = function(){
  20. Promise.resolve().then(function(){
  21. return light(3000, red);
  22. }).then(()=>{
  23. return light(2000, green);
  24. }).then(()=>{
  25. return light(1000, yellow);
  26. }).then(()=>{
  27. step();
  28. })
  29. }
  30. //开始轮训
  31. step();

3. Promise 的局限性

  1. 错误被吃掉

    首先我们要理解,什么是错误被吃掉,是指错误信息不被打印吗?
    并不是,举个例子:

  1. //在这种情况下,因为 throw error 的缘故,代码被阻断执行,并不会打印 233333,再举个例子:
  2. throw new Error('error');
  3. console.log(233333);
  4. //代码依然会被阻断执行,是因为如果通过无效的方式使用 Promise,并且出现了一个错误阻碍了正常 Promise 的构造,
  5. //结果会得到一个立刻跑出的异常,而不是一个被拒绝的 Promise。
  6. const promise = new Promise(null);
  7. console.log(233333);
  8. //正常的promise, 正常打出23333,说明 Promise 内部的错误不会影响到 Promise 外部的代码,
  9. 而这种情况我们就通常称为 “吃掉错误”。
  10. let promise = new Promise(() => {
  11. throw new Error('error')
  12. });
  13. console.log(2333333);

4. Generator 巩固

5. Async巩固

6. 异步实战

功能

查找指定目录下的最大的文件,从回调函数-> Promise->Generator->Async

需要用的Node的Api

1) fs.readdir: 用于读取目录,返回一个包含文件和目录的数组。
2) fs.stat: stat 方法的参数是一个文件或目录,它产生一个对象,该对象包含了该文件或目录的具体信息。此外,该对象还有一个 isFile() 方法可以判断正在处理的到底是一个文件,还是一个目录。

开发思路

开始前,需要斟酌具体怎么做,构想思路:

  1. fs.readdir 获取指定目录的内容信息
  2. 循环遍历内容信息,使用 fs.stat 获取该文件或者目录的具体信息
  3. 将具体信息储存起来
  4. 当全部储存起来后,筛选其中的是文件的信息
  5. 遍历比较,找出最大文件
  6. 获取并返回最大文件

    具体的代码开发

    回调运用,promise运用, generator, async

  1. const path = require('path');
  2. const fs = require('fs');
  3. //正常的获取文件
  4. function cGetMaxFile (filePath, cb){
  5. fs.readdir(filePath, (err, files)=>{
  6. //console.log("file", err,files);
  7. //如果err 报错的话,则return、
  8. if(err) return cb(err);
  9. var counter = files.length-1; //读取的文件的个数
  10. var stats = []; //具体的详情, 用来存储
  11. files.forEach((file, index)=>{
  12. //每一个文件依次去获取文件信息
  13. fs.stat(path.join(filePath, file), function(er, stat){
  14. if(er){ return cb(er)}
  15. stats[index] = stat; //存储每一个文件的具体信息
  16. //代表是最后一个文件时,则执行筛选工作
  17. if(index === counter-1 ){
  18. //首先要判断读取到的路径是不是文件,是文件才读取信息
  19. let largest = stats.filter(function(stat) { return stat.isFile() }).reduce(function (prev, next){
  20. if(prev.size > next.size) return prev;
  21. return next;
  22. })
  23. //得到值成功回调
  24. cb(null,largest )
  25. }
  26. })
  27. })
  28. })
  29. }
  30. //通过promise验证
  31. function pGetMaxFile(filePath){
  32. //第一步需要去获取文件列表
  33. const readDir = function (dir){
  34. return new Promise((resolve, reject)=>{
  35. fs.readdir(dir,(err, files)=>{
  36. if(err) reject(err);
  37. //得到所有文件的路径集合
  38. resolve(files);
  39. })
  40. })
  41. }
  42. //第二部需要去获取单个文件的具体大小详情
  43. const stat = function (path){
  44. return new Promise((resolve, reject)=>{
  45. //获取单个文件的具体信息
  46. fs.stat(path,(err,stat)=>{
  47. if(err) reject(err);
  48. resolve(stat);
  49. })
  50. })
  51. }
  52. return readDir(filePath)
  53. .then(files=>{
  54. //得到了所有的文件,
  55. //需要想办法把所有的文件转化成文件详情数组,然后返回
  56. let promises = files.map((file)=>stat(path.join(filePath, file)))
  57. return Promise.all(promises).then((stats)=>{
  58. //这里就会得到所有的file的详情, 然后进行比较
  59. return {stats}
  60. })
  61. }).then(({stats})=>{
  62. let largest = stats.filter((stat)=>{return stat.isFile()})
  63. .reduce((prev, next)=>{
  64. if(prev.size > next.size) return prev;
  65. return next;
  66. })
  67. return largest
  68. })
  69. }
  70. //Generator
  71. //var co = require('co')
  72. var co = require('co')
  73. function *gGetMaxFile(filepath){
  74. //第一步需要去获取文件列表
  75. const readDir = function (dir){
  76. return new Promise((resolve, reject)=>{
  77. fs.readdir(dir,(err, files)=>{
  78. if(err) reject(err);
  79. //得到所有文件的路径集合
  80. resolve(files);
  81. })
  82. })
  83. }
  84. //第二部需要去获取单个文件的具体大小详情
  85. const stat = function (path){
  86. return new Promise((resolve, reject)=>{
  87. //获取单个文件的具体信息
  88. fs.stat(path,(err,stat)=>{
  89. if(err) reject(err);
  90. resolve(stat);
  91. })
  92. })
  93. }
  94. const files = yield readDir(filepath);
  95. const stats = yield files.map((file)=>{
  96. return stat(path.join(filepath, file));
  97. })
  98. let largest = stats
  99. .filter(function(stat) { return stat.isFile() })
  100. .reduce((prev, next) => {
  101. if (prev.size > next.size) return prev
  102. return next
  103. })
  104. return largest;
  105. }
  106. //Async测试
  107. async function aGetMaxFile(filePath){
  108. //读取文件路径
  109. const readDir = (filePath)=>{
  110. return new Promise((resolve, reject)=>{
  111. fs.readdir(filePath,(err, files)=>{
  112. if(err) reject(err);
  113. resolve(files);
  114. })
  115. })
  116. }
  117. //根据文件路径获取文件详细信息
  118. const stat = (filePath)=>{
  119. return new Promise((resolve, reject)=>{
  120. fs.stat(filePath,(err, stat)=>{
  121. if(err) reject(err);
  122. resolve(stat);
  123. })
  124. })
  125. }
  126. //依次获取
  127. let files = await readDir(filePath);
  128. let promises= files.map((file)=>stat(path.join(filePath,file)));
  129. let stats = await Promise.all(promises);
  130. let largest = stats.filter((stat)=>{return stat.isFile()})
  131. .reduce((prev, next)=>{
  132. if(prev.size>next.size) return prev;
  133. return next;
  134. })
  135. return largest;
  136. }
  137. //常规的回调函数
  138. cGetMaxFile('./dist/train', (err, file)=>{
  139. //成功调用并得到对象
  140. console.log("err", err, file);
  141. })
  142. //Promise的调用
  143. pGetMaxFile('./dist/train').then((data)=>{
  144. console.log("data", data);
  145. });
  146. //Generator
  147. //通过这样的方式来调用
  148. co(gGetMaxFile,'./dist/train' ).then((data)=>{
  149. console.log("data", data);
  150. }).catch(function(error) {
  151. console.log(error);
  152. });
  153. //async的调用
  154. aGetMaxFile('./dist/train').then((maxfile)=>{
  155. console.log("maxFile", maxfile);
  156. }).catch(function(error) {
  157. console.log(error);
  158. });

7. class底层原理

ES6中的class与ES5中构造函数的对应, class只是一种语法糖;

1. constructor

  1. //ES6中:
  2. class Person {
  3. constructor(name) {
  4. this.name = name;
  5. }
  6. sayHello() {
  7. return 'hello, I am ' + this.name;
  8. }
  9. }
  10. var lambGirl = new Person('lambGirl');
  11. lambGirl.sayHello(); // hello, I am lambGirl
  12. //ES5
  13. function Person(name){
  14. this.name = name;
  15. }
  16. Person.prototype.sayHello = function(){
  17. return 'hello, I am ' + this.name;
  18. }
  19. var lambGirl = new Person('lambGirl');
  20. lambGirl.sayHello(); // hello, I am lambGirl
  21. //我们可以看到 ES5 的构造函数 Person,对应 ES6 的 Person 类的 constructor 方法。

值得注意的是:类的内部所有定义的方法,都是不可枚举的(non-enumerable)
验证一下:

  1. 以上面的例子为例,在 ES6 中:
  2. Object.keys(Person.prototype); // []
  3. Object.getOwnPropertyNames(Person.prototype); // ["constructor", "sayHello"]
  4. ES5
  5. Object.keys(Person.prototype); // ['sayHello'] //可以被枚举
  6. Object.getOwnPropertyNames(Person.prototype); // ["constructor", "sayHello"]

2. 实例属性

以前,我们定义实例属性,只能写在类的 constructor 方法里面。比如:

  1. class Person {
  2. constructor() {
  3. this.state = {
  4. count: 0
  5. };
  6. }
  7. }
  8. //现在的写法
  9. class Person {
  10. state = {
  11. count: 0
  12. };
  13. }
  14. ES5编译
  15. function Person() {
  16. this.state = {
  17. count: 0
  18. };
  19. }

3. 静态方法

所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上 static 关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

  1. class Person {
  2. static sayHello() {
  3. return 'hello';
  4. }
  5. }
  6. Person.sayHello() // 'hello'
  7. var LambGirl = new Person();
  8. LambGirl.sayHello(); // TypeError: LambGirl.sayHello is not a function , 静态私有方法,不能被继承
  9. 对应ES5
  10. function Person(){}
  11. Person.sayHello = function() {
  12. return 'hello';
  13. };
  14. Person.sayHello(); // 'hello'
  15. var LambGirl = new Person();
  16. LambGirl.sayHello(); // TypeError: LambGirl.sayHello is not a function , 静态私有方法,不能被继承

4.new 调用

值得注意的是:类必须使用 new 调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用 new 也可以执行。

5. getter 和 setter

与 ES5 一样,在“类”的内部可以使用 get 和 set 关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

  1. ES6
  2. class Person{
  3. get name(){
  4. return 'kevin';
  5. }
  6. set name(newName){
  7. console.log("new name 为:"+ newName)
  8. }
  9. }
  10. 对应到ES5
  11. function Person(name) {}
  12. Person.prototype = {
  13. get name() {
  14. return 'kevin';
  15. },
  16. set name(newName) {
  17. console.log('new name 为:' + newName)
  18. }
  19. }

6. 分析babel编译的class

1. ES6代码

  1. class Person {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. sayHello() {
  6. return 'hello, I am ' + this.name;
  7. }
  8. static onlySayHello() {
  9. return 'hello'
  10. }
  11. get name() {
  12. return 'lambGirl';
  13. }
  14. set name(newName) {
  15. console.log('new name 为:' + newName)
  16. }
  17. }

2. 对应到 ES5 的代码应该是:

  1. function Person(name) {
  2. this.name = name;
  3. }
  4. Person.prototype = {
  5. sayHello: function () {
  6. return 'hello, I am ' + this.name;
  7. },
  8. get name() {
  9. return 'lambGirl';
  10. },
  11. set name(newName) {
  12. console.log('new name 为:' + newName)
  13. }
  14. }
  15. Person.onlySayHello = function () {
  16. return 'hello'
  17. };

3. babel编译后

  1. "use strict";
  2. function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return right[Symbol.hasInstance](left); } else { return left instanceof right; } }
  3. function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  4. //为元素声明属性
  5. function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
  6. function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
  7. var Person =
  8. /*#__PURE__*/
  9. function () {
  10. function Person(name) {
  11. /**
  12. _classCallCheck 的作用是检查 Person 是否是通过 new 的方式调用,
  13. 在上面,我们也说过,类必须使用 new 调用,否则会报错。
  14. * _classCallCheck(instance, Constructor)
  15. */
  16. _classCallCheck(this, Person);
  17. this.name = name;
  18. }
  19. /**
  20. * Constructor: 构造函数,Person
  21. * protoProps: 方法属性组
  22. * staticProps: 静态属性配置 static
  23. * _createClass(Constructor, protoProps, staticProps)
  24. *
  25. */
  26. _createClass(Person, [{
  27. key: "sayHello",
  28. value: function sayHello() {
  29. return 'hello, I am ' + this.name;
  30. }
  31. }, {
  32. key: "name",
  33. get: function get() {
  34. return 'kevin';
  35. },
  36. set: function set(newName) {
  37. console.log('new name 为:' + newName);
  38. }
  39. }], [{
  40. key: "onlySayHello",
  41. value: function onlySayHello() {
  42. return 'hello';
  43. }
  44. }]);
  45. return Person;
  46. }();
  47. // 在其中,又生成了一个 defineProperties 辅助函数,使用 Object.defineProperty 方法添加属性。

默认 enumerable 为 false(这个是可以设置),configurable 为 true,这个在上面也有强调过,是为了防止 Object.keys() 之类的方法遍历到。然后通过判断 value 是否存在,来判断是否是 getter 和 setter。如果存在 value,就为 descriptor 添加 value 和 writable 属性,如果不存在,就直接使用 get 和 set 属性。

8. class的继承

ES5的原型继承图

image.png

ES6的继承关系

总结一下:
1)Child.proto == Parent: 子类的 proto 属性,表示构造函数的继承,总是指向父类。
2) child.prototype.proto = Parent.prototype
子类 prototype 属性的 proto 属性,表示方法的继承,总是指向父类的 prototype 属性。
image.png

extends 关键字后面可以跟多种类型的值。
class B extends A {
}
上面代码的 A,只要是一个有 prototype 属性的函数,就能被 B 继承。由于函数都有 prototype 属性(除了 Function.prototype 函数),因此 A 可以是任意函数。
除了函数之外,A 的值还可以是 null,当 extend null 的时候:

  1. class A extends null {
  2. }
  3. console.log(A.__proto__ === Function.prototype); // true
  4. console.log(A.prototype.__proto__ === undefined); // true


Babel 编译

编译前源码

  1. class Parent {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. }
  6. class Child extends Parent {
  7. constructor(name, age) {
  8. super(name); // 调用父类的 constructor(name)
  9. this.age = age;
  10. }
  11. }
  12. var child1 = new Child('lambGirl', '20');
  13. console.log(child1);

编译后:

  1. "use strict";
  2. //类型判断
  3. function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol")
  4. { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
  5. //函数帮助确定调用父类构造函数的返回值
  6. //根据 Parent 构造函数的返回值类型确定子类构造函数 this 的初始值 _this。
  7. function _possibleConstructorReturn(self, call) {
  8. if (call && (_typeof(call) === "object" || typeof call === "function")) {
  9. return call;
  10. }
  11. return _assertThisInitialized(self);
  12. }
  13. function _assertThisInitialized(self) {
  14. if (self === void 0) {
  15. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  16. }
  17. return self;
  18. }
  19. function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
  20. //函数帮助实现继承
  21. function _inherits(subClass, superClass) {
  22. // extend 的继承目标必须是函数或者是 null, 如果不是这两种则报异常
  23. if (typeof superClass !== "function" && superClass !== null) {
  24. throw new TypeError("Super expression must either be null or a function");
  25. }
  26. //类似于 ES5 的寄生组合式继承,使用 Object.create,
  27. //设置子类 prototype 属性的 __proto__ 属性指向父类的 prototype 属性
  28. subClass.prototype = Object.create(superClass && superClass.prototype, {
  29. constructor: { value: subClass, writable: true, configurable: true }
  30. });
  31. // 设置子类的 __proto__ 属性指向父类
  32. if (superClass) _setPrototypeOf(subClass, superClass);
  33. }
  34. //Child._proto_ = Parent;
  35. /*
  36. * o: 代表子类
  37. p: 代表父类
  38. */
  39. function _setPrototypeOf(o, p) {
  40. _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
  41. //这只子类的_proto = Parent
  42. o.__proto__ = p; return o;
  43. };
  44. return _setPrototypeOf(o, p);
  45. }
  46. function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return right[Symbol.hasInstance](left); } else { return left instanceof right; } }
  47. //做ES6构造函数的校验,是否是new关键字调用
  48. function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  49. var Parent = function Parent(name) {
  50. _classCallCheck(this, Parent);
  51. this.name = name;
  52. };
  53. var Child =
  54. /*#__PURE__*/
  55. function (_Parent) {
  56. //先实现继承,
  57. /**
  58. *1. 实现原型链上的继承: Child.prototype._proto = Parent.prototype
  59. *2. 构造函数上的继承 Child._proto = Parent
  60. */
  61. _inherits(Child, _Parent);
  62. function Child(name, age) {
  63. var _this;
  64. _classCallCheck(this, Child);
  65. //使得子类型也可以调用父类型的方法。// 调用父类的 constructor(name)
  66. _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name));
  67. _this.age = age;
  68. return _this;
  69. }
  70. return Child;
  71. }(Parent);
  72. var child1 = new Child('lambGirl', '20');
  73. console.log(child1);

关于 Object.create(),一般我们用的时候会传入一个参数,其实是支持传入两个参数的,第二个参数表示要添加到新创建对象的属性,注意这里是给新创建的对象即返回值添加属性,而不是在新创建对象的原型对象上添加。
比如

  1. // 创建一个以另一个空对象为原型,且拥有一个属性 p 的对象
  2. const o = Object.create({}, { p: { value: 42 } });
  3. console.log(o); // {p: 42}
  4. console.log(o.p); // 42

针对babel编译中的代码给一个解释

以父元素supClass为原型,切拥有一个构造函数constructor(这里默认就是不可以被枚举)

  1. subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });

_possibleConstructorReturn

  1. //强行解释一波
  2. Child._Proto_ == Parent;
  3. 要不就获取Child的原型 Child.prototype._proto = Parent.Constructor
  4. var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name));
  5. ===============这句话简介==================
  6. //使得Child可以调用Parent的方法
  7. var _this = _possibleConstructorReturn(this,Parent.call(this, name));