# 刷题记录

# 1. 能过的

日期 题目 难度 备注
07.02 正则表达式匹配 困难 还算熟练
07.03 有序矩阵第k小的元素 中等 能过,但不是最优解
07.03 股票系列 简单/中等/困难 比较熟练
07.04 打家劫舍系列 简单/中等 比较熟练
07.05 通配符匹配 困难 有思路,小修补
07.05 课程表I 中等 有思路,小修补
07.06 不同路径 II 中等 最开始思路超时,换解法通过
07.06 括号生成 中等 能写DFS,但不能DP
07.06 下一个排列 中等 没太大问题
07.07 在排序数组中查找元素的第一个和最后一个位置 中等 能过,但冗余,还有点卡手
07.07 二叉树的先序遍历 中等 应该掌握了,还要练习
07.07 二叉树的中序遍历 中等 能独立思考并写出来,还要练习
07.07 二叉树的后序遍历 困难 勉强掌握,还要练习
07.07 组合总和 中等 没太大问题
07.10 找到所有数组中消失的数字 简单 不使用额外空间和O(n)的限制,可以更熟练一点
07.15 股票价格跨度 中等 没问题
07.17 三数之和 中等 oklll
07.17 环形链表 II 中等 没问题,注意Floyd算法
07.31 接雨水 困难 只写对了一种解法,其他解法也很妙。
08.05 474. 一和零 中等 还好吧

# 2. 没掌握的

日期 题目 难度 备注
07.03 寻找正序数组的中位数 困难 知道解法,不会写
07.04 最长有效括号 困难 两解法,不会写,也都想不到
07.05 鸡蛋掉落 困难 勉强有思路,不会写
07.05 盛最多水的容器 中等 好写,但第一次做没思路
07.06 课程表 III 困难 鲨了我吧!!!
07.06 搜索旋转排序数组 中等 我知道是二分法的变种,但是,操你妈的,具体怎么变种我想不到
07.07 不同的二叉搜索树 II 中等 有思路,但,这么巧妙的递归,啊这...
07.08 寻找峰值 中等 这种题别再栽跟头
07.08 寻找旋转排序数组中的最小值 中等 别每次都靠试,要自己去debug,检查
07.08 在排序数组中查找元素的第一个和最后一个位置 中等 这个题我是真的不熟练
07.09 恢复空格 中等 能过,但卡手的一匹
07.09 颜色分类 中等 一般吧,知道思路但细枝末节很差。
07.10 多数元素 简单 注意掌握摩尔投票算法
07.10 把二叉搜索树转换为累加树 简单 回溯还没掌握
07.11 乘积最大子数组 中等 题还行,但是思路很差,思路的实现也不好
07.11 最大正方形 中等 题还好,思路不行,只能想到暴力
07.12 地下城游戏 困难 思路不大好,很勉强
07.12 搜索二维矩阵 II 中等 勉强能想到,但还需要强化思路
07.18 完全平方数 中等 有很大进步,再多练习
07.18 和为K的子数组 中等 有进步,还要再尝试
07.19 戳气球 困难 其实也没有那么变态难
07.19 四数之和 中等 一点也没思路啊,白做三数之和了
07.21 不同的二叉搜索树 II 中等 未通过
07.25 410. 分割数组的最大值 困难 完全没思路
07.28 面试题 02.06. 回文链表 简单 卡手
07.28 146. LRU缓存机制 中等 一般吧
07.30 160. 相交链表 简单 忘记了
07.30 面试题 08.05. 递归乘法 中等 不会
08.01 76. 最小覆盖子串 困难 看了题解能写
08.01 632. 最小区间 困难 看了题解能写
08.02 消失的两个数字 困难 丢了丢了
08.02 114. 二叉树展开为链表 中等 真难
08.03 322. 零钱兑换 中等 卡手,卡手,卡手
08.03 518. 零钱兑换 II 中等 好一点,好不到哪去
08.04 207. 课程表 中等 一般般,不算快
08.05 25. K 个一组翻转链表 困难 菜是原罪
08.05 152. 乘积最大子数组 中等 很差劲
08.09 93. 复原IP地址 中等 能做但卡手
08.21 1262. 可被三整除的最大和 中等 理解的不太好

# 3. 再刷剑指offer

日期 题目
07.21 剑指 Offer 11. 旋转数组的最小数字
07.21 剑指 Offer 13. 机器人的运动范围
07.23 剑指 Offer 15. 二进制中1的个数
07.23 剑指 Offer 19. 正则表达式匹配
07.23 剑指 Offer 20. 表示数值的字符串
07.23 剑指 Offer 26. 树的子结构
07.24 剑指 Offer 33. 二叉搜索树的后序遍历序列
07.27 剑指 Offer 41. 数据流中的中位数
07.27 剑指 Offer 42. 连续子数组的最大和
07.27 剑指 Offer 43. 1~n整数中1出现的次数
07.29 剑指 Offer 45. 把数组排成最小的数
07.29 剑指 Offer 46. 把数字翻译成字符串
07.29 剑指 Offer 48. 最长不含重复字符的子字符串
07.29 剑指 Offer 49. 丑数
07.30 剑指 Offer 51. 数组中的逆序对
07.30 剑指 Offer 65. 不用加减乘除做加法
08.07 剑指 Offer 56 - I. 数组中数字出现的次数
08.07 剑指 Offer 56 - II. 数组中数字出现的次数 II
08.07 剑指 Offer 57 - II. 和为s的连续正数序列
08.08 剑指 Offer 60. n个骰子的点数
08.08 剑指 Offer 62. 圆圈中最后剩下的数字
08.08 剑指 Offer 64. 求1+2+…+n
08.08 剑指 Offer 65. 不用加减乘除做加法
08.08 剑指 Offer 66. 构建乘积数组
08.09 剑指 Offer 68 - II. 二叉树的最近公共祖先

# 4. 手写代码系列

# 4.1 new

/*
1.创建一个全新的对象。
2.被执行`[[Prototype]]`链接,也就是`__proto__`链接。
3.使`this`指向新创建的对象。
4.通过`new`创建的每个对象将最终被`[[Prototype]]`链接到这个函数的`prototype`上。
5.如果函数没有返回对象类型Object(包含Function,Array,Date,RegExg,Error),那么`new`表达式中的函数调用将返回该对象引用。
*/
function _new(fn) {
  let res = {};
  if (fn.prototype !== null) {
    res.__proto__ = fn.prototype;
  }
  let args = [...arguments].slice(1);
  let ret = fn.apply(res, args);
  if ((typeof ret === 'object' || typeof ret === 'function') && ret !== null) {
    return ret;
  } else {
    return res;
  }
}

var obj = _new(A, 1, 2);
// <=>
var obj = new A(1, 2);
/*
我自己的理解,为了清楚,说的啰嗦一点。
首先,创建一个空的对象。
然后要对这个对象进行链接,链接有两部分:
1.该对象,作为child,要指向他的parent,也就是它的构造函数(即new函数传入的第一个参数)的prototype。
  这一步的作用是如果以后要访问该对象中的某个属性,属性不存在的时候就可以通过原型链向上查找,而这一步就是建议原型链的链接。
2.在构造函数里面,可能有一些赋值(比如 this.name = "range"),我们需要对这个新的对象进行一个类似初始化的操作
  这一步的作用是,将构造函数的初始化的一些值,对当前的新对象进行作用。
最后,需要分情况:
1.如果这个函数有返回值,如果不是 object/function 类型,就使用原构造函数的返回值,而不是我们根绝构造函数创建的新对象。
这里要注意的是,有的函数会返回一些基本的数据类型 比如数字,就像我们平时使用 new 的时候一样,如果函数的返回值是 数字,那么忽略它,然后返回我们创建的新对象。
2.如果这个函数没有设置返回值,那么就直接返回我们创建的新对象。
*/ 

# 4.2 call

/*
1.将函数设为对象的属性
2.执行并删除这个函数
3.指定`this`到函数并传入给定参数执行函数
4.如果不传入参数,默认指向为window
*/
Function.prototype._call = function(thisArg = window) {
  thisArg.fn = this;
  let args = [...arguments].slice(1);
  let ret = thisArg.fn(...args);
  delete thisArg.fn;
  return ret;
}
/*
首先,函数需要有一个执行者,这个执行者可以是用户创建的某一个对象实例`obj`,也可以是浏览器的`window`,不管是谁,必须要有。
所以这里`thisArg`的默认值为`window`,形如`func._call()`这样的调用其实就是`window`在调用。
而形如`obj.func()`的调用,`obj`就是上面函数中的`thisArg`,这是默认传入的第一个参数。
`this`则指的是调用`_call`所调用的函数,用完了要删除,不然这个函数就绑定给调用它的对象了。
比如:以`func._call()`的形式调用函数,也就是`window`调用,如果不删除,则调用完之后,`window.func()`还可以继续执行,这样不好,因为我们就好像只是路过,借用了一下`window`,但是却给把我们自己的东西忘记拿走了。
接着通过`slice`选取第一个以外的参数,这些是传给函数的参数,然后把参数传给这个函数并得到执行的结果 ret,然后返回
*/

# 4.3 apply

// 与`call`不同的地方在于传入的参数(也就是`arguments[1]`)是`Array`
Function.prototype._apply = function (thisArg = window) {
  thisArg.fn = this;
  let res;
  if (arguments[1]) {
    res = thisArg.fn(...arguments[1]);
  } else {
    res = thisArg.fn();
  }
  delete thisArg.fn;
  return res;
}

# 4.4 bind

Function.prototype._bind = function (thisArg) {
  // 存储函数本身
  let self = this;
  let args = [...arguments].slice(1)

  let bound = function() {
    // args是之前使用·bind·绑定的参数
    // (当前花括号的代码块内的)·arguments·是在·bind·操作之后,其他的对象(比如·window·)调用·bound·函数的时候传入的参数
    // 所以在调用·bound·的时候,需要将·bind·操作绑定的函数,和后面自主传入的参数拼接起来,得到·finalArgs·
    let boundArgs = [...arguments]
    let finalArgs = args.concat(boundArgs);


    if (this instanceof bound) {
      // 因为·bind·是返回一个函数,所以这个函数可以作为构造函数加·new·创建一个实例对象
      // 而·new·出来的这个实例对象的优先级高于·bind·,所以这里需要判断·this·的指向
      // 这里是判断是否是通过·new·来创建对象的,如果是·new·上面的语句就是·true·
      // 为什么使用`this instanceof bound`来判断是不是使用了·new·?
      // 因为·new·的操作中第二步为,obj.__proto__ = constructor.prototype
      // 这里的`this`就是指 new 的过程中创建的新的对象 obj
      // constructor 就是这个 bound

      function F () {}
      F.prototype = this.prototype;
      bound.prototype = new F();
      return this;
    } else {
      // 修改·this·的指向,将合并的参数传递给·self·函数,并执行·self·,最后返回执行结果。
      return self.apply(thisArg, finalArgs);
    }
  }

  return bound;
}
// `bind`的实现需要考虑实例化后对原型链的影响。
// 注意这里的`this`是指调用`bind`的对象(也就是一个`function`),不是第一个传入的对象,第一个传入的对象是`thisArg`。
// `bind`返回的是一个新的函数,这个函数永久性(不可再更改)绑定了传入给`bind`的第一个参数,后面的参数作为该函数的调用参数。
// 但其实上面的·instanceof·并不准确,可以使用·ES6·的·new.target·解决
// 举例说明

function Person(name) {
  if (this instanceof Person){
    // 这里的·this·我不太确定是什么,打印输出来看,应该是正在·new·的一个对象,只不过这个对象目前还是空的,还没有被赋值
    this.name = name;
    console.log('name', name);
  } else {
    throw new Error("必须通过new关键字来调用Person");
  }
}

let person1 = new Person("range"); // 正常创建了一个对象
let person2 = Person.call(person1, "sirius"); // 这里创建的对象是一个 undefined,但是创建的这句并未抛出错误
// 我这里还有很大的疑惑
// Person.call(person, "sirius") 这句话本来就没有返回值,person2 的值为·undefined·应该没什么毛病啊
// 而且这句话其实是生效了,·person1·的name变成了·sirius·

// 解决方法
function Person(name) {
  if (typeof new.target !== 'undefined') {
    // 这里的判读就更加直观可读
    this.name = name;
    console.log('name', name);
  } else {
    throw new Error("必须通过new关键字来调用Person");
  }
}

let person1 = new Person("range"); // name: range
let person2 = Person.call(person1, "sirius"); // 必须通过new关键字来调用Person

# 4.5 debounce

<!-- 无防抖 -->

<div id="content"
    style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div>
  <script>
    let num = 1;
    const content = document.getElementById('content');
    function count() {
      content.innerHTML = num++;
    };
    content.onmousemove = count;
  </script>
// 非立即执行
// 触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

function debounce(func, delay) {
  let timeout;
  return function () {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(this, arguments)
    }, delay);
  }
}

// html中修改
content.onmousemove = debounce(count, 100);
// 立即执行
// 触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。

function debounce(func, delay) {
  let timeout;
  return function () {
    const callNow = !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      timeout = null;
    }, delay);
    if (callNow) {
      func.apply(this, arguments);
    }
  }
}

content.onmousemove = debounce(count, 100);

# 4.6 throttle

// 时间戳 实现
function throttle(func, delay) {
  let pre = 0;
  return function() {
    let now = Date.now();
    if (now - pre > delay) {
      func.apply(this, arguments);
      pre = now;
    }
  }
}
// 定时器 实现
function throttle(func, delay) {
  let timeout;
  return function () {
    if (!timeout) {
      func.apply(this, arguments);
      timeout = setTimeout(() => {
        timeout = null;
      }, delay)
    }
  };
}

# 4.7 deepClone

// 深拷贝
function deepClone(obj) {
  if (obj === null) return obj;
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  if (typeof obj !== "object") return obj;
  let cloneObj = new obj.constructor();
  for (let key in obj) {
    if(obj.hasOwnProperty(key)){
      cloneObj[key] = deepClone(obj[key]);
    }
  }
  return cloneObj;
}

# 4.8 shallowClone

// 浅拷贝
function shallowClone(obj) {
  let cloneObj = {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = obj[key];
    }
  }
  return cloneObj
}

# 4.9 generator

function* genDemo() {
    console.log(" 开始执行第一段 ")
    yield 'generator 2'
 
    console.log(" 开始执行第二段 ")
    yield 'generator 2'
 
    console.log(" 开始执行第三段 ")
    yield 'generator 2'
 
    console.log(" 执行结束 ")
    return 'generator 2'
}
 
console.log('main 0')
let gen = genDemo()
console.log(gen.next().value)
console.log('main 1')
console.log(gen.next().value)
console.log('main 2')
console.log(gen.next().value)
console.log('main 3')
console.log(gen.next().value)
console.log('main 4')

# 4.10 inherit

function Parent() {
  this.name = "parent";
}

Parent.prototype.getParentName = function() {
  console.log(this.name);
}

function Child() {
  this.name = "child";
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;
/*
思考:为什么不写成 Child.prototype = Parent.prototype ?
先思考,prototype什么要叫原型对象,而不是直接叫做原型?
我猜测:说白了,原型对象其实也是一个存在的实例化的对象
只不过这个东西一般是隐藏起来的,
*/ 

# 4.11 AJAX

function request(method, url, data) {
  return new Promise((resolve, reject) => {
    let handler = function () {
      if (this.readyState !== 4) return;
      if (this.status === 200) {
        return resolve(this.response);
      } else {
        return reject(this.statusText);
      }
    }
    let xhr = XMLHttpRequest();
    xhr.onreadystatechange = handler;

    if (method.toLowerCase() === 'get') {
      url += '?';
      for (let key in data) {
        url += `${key}=${data[key]}&`;
      }
      url = url.slice(0, url.length - 1);

      xhr.open(method, url);
      xhr.send();
    } else {
      xhr.open(method, url);
      xhr.send(data);
    }
  })
}

# 4.12 Promise

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function MyPromise(executor) {
  const self = this;
  self.value = null;
  self.error = null;
  self.status = PENDING;
  self.onFulfilledCallbacks = [];
  self.onRejectedCallbacks = [];

  function resolve(value) {
    if (value instanceof MyPromise) {
      return value.then(resolve, reject);
    }
    if (self.status === PENDING) {
      setTimeout(() => {
        self.status = FULFILLED;
        self.value = value;
        self.onFulfilledCallbacks.forEach((callback) => callback(self.value));
      }, 0);
    }
  }

  function reject(error) {
    if (self.status === PENDING) {
      setTimeout(function () {
        self.status = REJECTED;
        self.error = error;
        self.onRejectedCallbacks.forEach((callback) => callback(self.error));
      }, 0);
    }
  }
  
  try {
    executor(resolve, reject);
  } catch (e) {
    reject(e);
  }
}

MyPromise.prototype.then = function (onFulfilled, onRejected) {
  if (this.status === PENDING) {
    this.onFulfilledCallbacks.push(onFulfilled);
    this.onRejectedCallbacks.push(onRejected);
  } else if (this.status === FULFILLED) {
    onFulfilled(this.value);
  } else {
    onRejected(this.error);
  }
  return this;
};

MyPromise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected);
};


// 但这个 promise 还有几个缺陷:

// 1.then 方法返回的是 this,本应该返回一个新的 promise 实例。因此,不支持串行异步任务
// 诸如:串行异步任务:p.then(f1).then(f2).then(f3).catch(errorLog),分别读取文件 f1、f2、f3 并且在读取完之后打印
// 但这个会一直输出 f1、f1、f1,因为这几个任务都放到了 p1 的回调队列 onFulfilledCallbacks 里面

// 2.一个状态为 resolve / reject 的 promise,在调用 then 的时候,也不应该被立即执行

// 3.在代码执行了函数 resolve() / reject() 之后,手写代码并没有马上将状态改为 resolved / rejected,而是等待 resolve() / reject() 的回调函数执行了之后,才更改状态,而在真正的 promise 中,是立即改变的。

# 4.13 heap

function heapify(heap, n) {
  let minN = n, size = heap.length;
  let left = 2 * n + 1, right = 2 * n + 2;
  if (left < heap.length && heap[left] < heap[minN]) minN = left;
  if (right < heap.length && heap[right] < heap[minN]) minN = right;
  if (minN !== n) {
    [heap[minN], heap[n]] = [heap[n], heap[minN]];
    heapify(heap, minN);
  }
}

function heapBuild(heap) {
  let lastParent = Math.floor(heap.length / 2) - 1;
  for (let i = lastParent; i >= 0; i--) {
    heapify(heap, i);
  }
}

function heapPush(heap, n) {
  heap.push(n);
  let node = heap.length - 1;
  while (node > 0) {
    node = Math.floor((node - 1) / 2);
    heapify(heap, node);
  }
}

function heapPop(heap) {
  if (!heap.length) return null;
  [heap[0], heap[heap.length - 1]] = [heap[heap.length - 1], heap[0]];
  let ans = heap.pop();
  heapify(heap, 0);
  return ans;
}


let arr = [1, 13, 21, 16, 18, 29, 3, 7, 2];
heapBuild(arr);
console.log(arr);         // [1, 2, 3, 7, 18, 29, 21, 13, 16]
heapPush(arr, 20);
console.log(arr);         // [1, 2, 3, 7, 18, 29, 21, 13, 16, 20]
let ans = heapPop(arr);
console.log(ans);         // 1
console.log(arr);         // [2, 7, 3, 13, 18, 29, 21, 20, 16]

# 4.14 Jsonp

// 原生
function jsonp(url, data, callback) {
  let dataStr = '?';
  for (let key in data) {
    dataStr += `${key}=${data[key]}&`
  }
  dataStr += 'callback=' + callback.name;

  let script = document.createElement('script');
  script.src = url + dataStr;
  document.head.appendChild(script);
}

# 4.15 fetch

fetch('https://example.com/movies.json')
	.then(function(response) {
    return response.json();
  })
	.then(function(myJson) {
  console.log(myJson);
	})

# 4.16 axios

// GET
axios({
  method:'get',
  url:'http://image.png',
})
  .then(function(response) {
  console.log(response);
});

// POST 
axios({
  method: 'post',
  url: '/user/12345',
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone'
  }
});

# 4.17 数组去重

// 1.Set
function unique (arr) {
  return Array.from(new Set(arr))
}
// 或者
[...new Set(arr)]

// 2.splice O(N^2)
function unique(arr){            
        for(var i = 0; i < arr.length; i++){
            for(var j = arr.length - 1; j > i; j--) {
                if(arr[i]==arr[j]){         //第一个等同于第二个,splice方法删除第二个
                    arr.splice(j,1);
                }
            }
        }
return arr;
}

// 3.indexOf
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array = [];
    for (var i = 0; i < arr.length; i++) {
        if (array .indexOf(arr[i]) === -1) {
            array .push(arr[i])
        }
    }
    return array;
}

// 4.sort
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return;
    }
    arr = arr.sort()
    var arrry= [arr[0]];
    for (var i = 1; i < arr.length; i++) {
        if (arr[i] !== arr[i-1]) {
            arrry.push(arr[i]);
        }
    }
    return arrry;
}

// 5.filter
function unique(arr) {
  return arr.filter(function(item, index, arr) {
    //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
    return arr.indexOf(item, 0) === index;
  });
}

# 4.18 快排时间复杂度

  • 最差情况:
    • 分为了「n - 1 个元素」和「0 个元素」两部分。
    • 𝑇(𝑛) = 𝑇(𝑛−1) + 𝑇(0) + Θ(𝑛) = 𝑇(𝑛−1) + Θ(𝑛),
    • 其中 𝑇(0) = Θ(1),空数组不用排
    • 解得:𝑇(𝑛) = Θ(𝑛^2)
  • 最优情况:
    • 均分为两个「n / 2 个元素」,或者是 「n / 2」+「n / 2 - 1」
    • 𝑇(𝑛) = 2𝑇(𝑛/2) + Θ(𝑛)
    • 可以递推,或者根据主定理,可以得到:𝑇(𝑛) = Θ(𝑛lg𝑛)

# 4.19 柯里化

  • 概念:将函数与传递给函数的参数结合在一起,产生出一个新的函数(有点工厂函数的意思?)

  • sum(2, 2, 3)(4, 5)(5).toValue() === 21
    
    function sum() {
      let args = [...arguments];
      let fn = function () {
        args.push(...arguments);
        return fn;
      };
      fn.toValue = function () {
        return args.reduce((x, y) => x + y);
      };
      return fn;
    }
    
    let ans = sum(2, 2, 3)(4, 5)(5);
    console.log(ans.toValue());
    
    // 什么是柯里化?将函数与传递给函数的参数结合在一起,产生出一个新的函数(有点工厂函数的意思?)
    Function.method('curry', function() {
      let args = arguments;
      let that = this;
      return function() {
        return that.apply(null, args.concat(arguments));
      };
    });
    
    // 柯里化:只传递给函数一部分参数来调用它,让它返回一个新函数去处理剩下的参数。
    const add = (x, y, z) => x + y + z;
    
    const curry = function (fn) {
      return function () {
        // 判断传入的参数是否满足fn所需要的参数数量
        if (arguments.length >= fn.length) {
          // 是,则返回函数执行的结果。
          return fn.apply(this, arguments);
        } else {
          // 否,则继续收集参数,bind给函数,并且返回这个bind
          // 注意这里,因为可能会多次收集参数,所以要递归curry
          // 而不是直接返回bind后的新函数。
          return curry(fn.bind(this, ...arguments));
        }
      };
    };
    
    const addCurry = curry(add);
    const addOne = addCurry(1);
    const addTwo = addOne(1);
    let ans = addOne(10, 10);
    console.log(ans); // 21
    let res = addTwo(10);
    console.log(res); // 12
    

# 4.20 promise.all()

Promise.prototype._all = function (iterable) {
  return new Promise((resolve, reject) => {
    let index = 0;
    for (const promise of iterable) {
      const currentIndex = index;
      promise.then(
          (value) => {
            if (anErrorOccurred) return;
            result[currentIndex] = value;
            elementCount++;
            if (elementCount === result.length) {
              resolve(result);
            }
          },
          (err) => {
            if (anErrorOccurred) return;
            anErrorOccurred = true;
            reject(err);
          }
      );
      index++;
    }
    if (index === 0) {
      resolve([]);
      return;
    }
    let elementCount = 0;
    let anErrorOccurred = false;
    const result = new Array(index);
  });
};

# 4.21 打印red,停1s,打印yellow,停2s,打印green,停4s。循环5次。

const red = () => console.log('red');
const green = () => console.log('green');
const yellow = () => console.log('yellow');

const light = function (delay, callback) {
  return new Promise(resolve => {
    callback();
    setTimeout(() => {
      resolve();
    }, delay);
  });
};

const step = function (times) {
  Promise.resolve()
      .then(() => light(1000, red))
      .then(() => light(2000, yellow))
      .then(() => light(4000, green))
      .then(() => {
        if (times > 1) return step(times - 1);
      });
};

step(5);

# 4.22 日期格式化函数

// 乞丐版
function formatDate(date) {
    let d = new Date(date);
  
    let year = '' + d.getFullYear();
    let month = '' + (d.getMonth() + 1);
    let day = '' + d.getDate();
    let hour = '' + d.getHours();
    let minute = '' + d.getMinutes();
    let second = '' + d.getSeconds();
  
    if (month.length < 2) month = '0' + month;
    if (day.length < 2) day = '0' + day;
    if (hour.length < 2) hour = '0' + hour;
    if (minute.length < 2) minute = '0' + minute;
    if (second.length < 2) second = '0' + second;
  
    return [year, month, day].join('-') + ' ' + [hour, minute, second].join(':');
}

// 完全版
function formatDate(date, fmt) {
  if (/(y+)/.test(fmt)) {
    fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
  }
  let o = {
    'M+': date.getMonth() + 1,
    'd+': date.getDate(),
    'h+': date.getHours(),
    'm+': date.getMinutes(),
    's+': date.getSeconds()
  };
  for (let k in o) {
    if (new RegExp(`(${k})`).test(fmt)) {
      let str = o[k] + '';
      fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
    }
  }
  return fmt;
}

function padLeftZero (str) {
  return ('00' + str).substr(str.length);
}

# 4.23 Promise异步编程题 - eat

// ES6 class
class Person {
  constructor() {
    this.tasks = [];
    setTimeout(()=> this.run(), 0);
  }

  eat(food) {
    this.tasks.push(() => {
      return new Promise((resolve) => {
        console.log(food);
        resolve();
      })
    });
    return this;
  }

  sleep(time) {
    this.tasks.push(() => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve()
        }, time);
      })
    });
    return this;
  }

  run() {
    this.tasks.reduce((pre, next) => {
      return pre.then(() => next());
    }, Promise.resolve());
  }
}

let person = new Person();
person.sleep(2000).eat('包子');
person.sleep(1000).sleep(2000).eat('饺子').sleep(3000).eat('吃了睡睡了吃');
// ES5 朴素版
function Person() {
  this.tasks = [];
  setTimeout(() => this.run(), 0);

  this.eat = function (food) {
    this.tasks.push(() => {
      return new Promise(resolve => {
        console.log(food);
        resolve();
      });
    });
    return this;
  };

  this.sleep = function (delay) {
    this.tasks.push(() => {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve();
        }, delay);
      });
    });
    return this;
  };

  this.run = function () {
    this.tasks.reduce((pre, next) => {
      return pre.then(() => next());
    }, Promise.resolve());
  };
}

let person = new Person();
person.sleep(2000).eat('包子');
person.sleep(1000).sleep(2000).eat('饺子').sleep(3000).eat('吃了睡睡了吃');

# 5.promise习题

# 5.1 习题一

new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
})
    .then(() => {
      console.log("外部第一个then");
      return new Promise((resolve, reject) => {
        console.log("内部promise");
        resolve();
      })
          .then(() => {
            console.log("内部第一个then");
          })
          .then(() => {
            console.log("内部第二个then");
          });
    })
    .then(() => {
      console.log("外部第二个then");
    });

# 5.2 习题二

new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
})
    .then(() => {
      console.log("外部第一个then");
      new Promise((resolve, reject) => {
        console.log("内部promise");
        resolve();
      })
          .then(() => {
            console.log("内部第一个then");
          })
          .then(() => {
            console.log("内部第二个then");
          });
    })
    .then(() => {
      console.log("外部第二个then");
    });

# 5.3 习题三

new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
})
    .then(() => {
      console.log("外部第一个then");
      let p = new Promise((resolve, reject) => {
        console.log("内部promise");
        resolve();
      });
      p.then(() => {
        console.log("内部第一个then");
      });
      p.then(() => {
        console.log("内部第二个then");
      });
    })
    .then(() => {
      console.log("外部第二个then");
    });

# 5.4 习题四

let p = new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
});
p.then(() => {
  console.log("外部第一个then");
  new Promise((resolve, reject) => {
    console.log("内部promise");
    resolve();
  })
      .then(() => {
        console.log("内部第一个then");
      })
      .then(() => {
        console.log("内部第二个then");
      });
});
p.then(() => {
  console.log("外部第二个then");
});

# 5.5 习题五

new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
})
    .then(() => {
      console.log("外部第一个then");
      new Promise((resolve, reject) => {
        console.log("内部promise");
        resolve();
      })
          .then(() => {
            console.log("内部第一个then");
          })
          .then(() => {
            console.log("内部第二个then");
          });
      return new Promise((resolve, reject) => {
        console.log("内部promise2");
        resolve();
      })
          .then(() => {
            console.log("内部第一个then2");
          })
          .then(() => {
            console.log("内部第二个then2");
          });
    })
    .then(() => {
      console.log("外部第二个then");
    });

# 5.6 习题六

new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
})
    .then(() => {
      console.log("外部第一个then");
      new Promise((resolve, reject) => {
        console.log("内部promise");
        resolve();
      })
          .then(() => {
            console.log("内部第一个then");
          })
          .then(() => {
            console.log("内部第二个then");
          });
      return new Promise((resolve, reject) => {
        console.log("内部promise2");
        resolve();
      })
          .then(() => {
            console.log("内部第一个then2");
          })
          .then(() => {
            console.log("内部第二个then2");
          });
    })
    .then(() => {
      console.log("外部第二个then");
    });

# 5.7 习题七

var p1 = new Promise(function(resolve, reject){
  resolve("success1");
  resolve("success2");
});

var p2 = new Promise(function(resolve, reject){
  resolve("success");
  reject("reject");
});

p1.then(function(value){
  console.log(value);
});

p2.then(function(value){
  console.log(value);
});

# 5.8 习题八

var p = new Promise(function(resolve, reject){
  resolve(1);
});
p.then(function(value){               //第一个then
  console.log(value);
  return value*2;
}).then(function(value){              //第二个then
  console.log(value);
}).then(function(value){              //第三个then
  console.log(value);
  return Promise.resolve('resolve'); 
}).then(function(value){              //第四个then
  console.log(value);
  return Promise.reject('reject');
}).then(function(value){              //第五个then
  console.log('resolve: '+ value);
}, function(err){
  console.log('reject: ' + err);
})

# 5.9 习题九

var p1 = new Promise( function(resolve,reject){
  foo.bar();
  resolve( 1 );	  
});

p1.then(
  function(value){
    console.log('p1 then value: ' + value);
  },
  function(err){
    console.log('p1 then err: ' + err);
  }
).then(
  function(value){
    console.log('p1 then then value: '+value);
  },
  function(err){
    console.log('p1 then then err: ' + err);
  }
);

var p2 = new Promise(function(resolve,reject){
  resolve( 2 );	
});

p2.then(
  function(value){
    console.log('p2 then value: ' + value);
    foo.bar();
  }, 
  function(err){
    console.log('p2 then err: ' + err);
  }
).then(
  function(value){
    console.log('p2 then then value: ' + value);
  },
  function(err){
    console.log('p2 then then err: ' + err);
    return 1;
  }
).then(
  function(value){
    console.log('p2 then then then value: ' + value);
  },
  function(err){
    console.log('p2 then then then err: ' + err);
  }
);

# 5.10 习题十

var p1 = Promise.resolve( 1 );
var p2 = Promise.resolve( p1 );
var p3 = new Promise(function(resolve, reject){
  resolve(1);
});
var p4 = new Promise(function(resolve, reject){
  resolve(p1);
});

console.log(p1 === p2); 
console.log(p1 === p3);
console.log(p1 === p4);
console.log(p3 === p4);

p4.then(function(value){
  console.log('p4=' + value);
});

p2.then(function(value){
  console.log('p2=' + value);
})

p1.then(function(value){
  console.log('p1=' + value);
})

# 5.11 习题十一

var p1 = new Promise(function(resolve, reject){
  resolve(Promise.resolve('resolve'));
});

var p2 = new Promise(function(resolve, reject){
  resolve(Promise.reject('reject'));
});

var p3 = new Promise(function(resolve, reject){
  reject(Promise.resolve('resolve'));
});

var p4 = new Promise(function(resolve, reject){
  reject(Promise.reject('reject'));
});

p1.then(
  function fulfilled(value){
    console.log('fulfilled1: ' + value);
  }, 
  function rejected(err){
    console.log('rejected1: ' + err);
  }
);

p2.then(
  function fulfilled(value){
    console.log('fulfilled2: ' + value);
  }, 
  function rejected(err){
    console.log('rejected2: ' + err);
  }
);

p3.then(
  function fulfilled(value){
    console.log('fulfilled3: ' + value);
  }, 
  function rejected(err){
    console.log('rejected3: ' + err);
  }
);

p4.then(
  function fulfilled(value){
    console.log('fulfilled4: ' + value);
  }, 
  function rejected(err){
    console.log('rejected4: ' + err);
  }
);

# 5.12 习题十二

const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve("success"), 3000);
});

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000);
});

p2
    .then(result => console.log("then1", result))
    .then(result => console.log("then2", result));

// 输出
// then1 success
// then2 undefined
// 正如上面的 promise 解决过程中所提到的
// 上面代码中,p1是一个 Promise,3 秒之后变为 fulfilled。p2的状态在 1 秒之后改变,resolve方法返回的是p1。由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为 fulfilled,导致触发 then 方法指定的回调函数。
// 因此,p2 接受了 p1 的状态,导致自己的状态无效了。

# 5.13 习题十三

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
const promise2 = promise1.then(() => {
  throw new Error('error!!!')
})

console.log('promise1', promise1)
console.log('promise2', promise2)

setTimeout(() => {
  console.log('promise1', promise1)
  console.log('promise2', promise2)
}, 2000)

# 5.14 习题14

Promise.resolve(1)
  .then((res) => {
    console.log(res)
    return 2
  })
  .catch((err) => {
    return 3
  })
  .then((res) => {
    console.log(res)
  })

# 5.15 习题十五

Promise.resolve()
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })

# 5.16 习题十六

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

# 5.17 习题十七

Promise.resolve()
  .then(function success (res) {
    throw new Error('error')
  }, function fail1 (e) {
    console.error('fail1: ', e)
  })
  .catch(function fail2 (e) {
    console.error('fail2: ', e)
  })

# 5.18 习题十八

setTimeout(() => {
  console.log('log-timeout');
}, 0);

process.nextTick(() => {
  console.log('tick');
});

const promise = new Promise(resolve => {
  console.log('log-promise');
  resolve('promise resolve');
});

(async () => {
  console.log('async start');
  const str = await promise;
  console.log(str);
})()

promise.then(() => {
  console.log('log-promise1-then');
})

console.log('log-end');

# 6. this习题

# 6.1 普通例子

function func(){
  console.log(this)
}

func();
<=>
func.call(undefined); // 可以简写为 func.call()

但这里打印出来的this并不是undefined,因为浏览器的规则:

“如果你传入的context是null或者undefined,那么window对象就是默认的context(严格模式下默认context是undefined)”

let obj = {
  foo: function() {
    console.log(this)
  }
}

obj.foo();
<=>
obj.foo.call(obj)

// 所以这里的this就是obj
let obj = {
  foo: function() {
    console.log(this)
  }
}

let bar = obj.foo;

// 考虑两者的输出
obj.foo()
bar()
// 答案
obj.foo()  // <=> obj.foo.call(obj), 输出obj
bar() // <=> bar.call() 没有传入context,this为undefined,浏览器返回一个默认的this,即window对象,所以打印的就是window

# 6.2 全局环境下的this

function f1 () {
  console.log(this);
}
function f2 () {
  'use strict'
  conso.log(this)
}

f1()
f2()
// 答案
f1(); // window
f2(); // undefined
const foo = {
  bar: 10,
  fn: function() {
    console.log(this);
    console.log(this.bar);
  }
}

let fn1 = foo.fn;
fn1();
// 答案
fn1(); // window, undefined
const foo = {
  bar: 10,
  fn: function() {
    console.log(this);
    console.log(this.bar);
  }
}

foo.fn();
// 答案
foo.fn(); // {bar: 10, fn: f} (也就是foo这个对象), 10 

在执行函数时,如果函数中的this是被上一级的对象所调用,那么this指向的就是上一级的对象;否则指向全局环境。

# 6.3 上下文对象调用中的this

const o1 = {
    text: 'o1',
    fn: function() {
        return this.text
    }
}
const o2 = {
    text: 'o2',
    fn: function() {
        return o1.fn()
    }
}
const o3 = {
    text: 'o3',
    fn: function() {
        var fn = o1.fn
        return fn()
    }
}

console.log(o1.fn())
console.log(o2.fn())
console.log(o3.fn())
// 答案
o1, o1, undefined

第一个 console 最简单,o1 没有问题。难点在第二个和第三个上面,关键还是看调用 this 的那个函数。

第二个 console 的 o2.fn(),最终还是调用 o1.fn(),因此答案仍然是 o1。

最后一个,在进行 var fn = o1.fn 赋值之后,是“裸奔”调用,因此这里的 this 指向 window,答案当然是 undefined

# 6.4 bind&call&apply改变this指向

const foo = {
  name: 'range',
  logName: function() {
    console.log(this.name);
  }
}

const bar = {
  name: 'mike'
}

console.log(foo.logName.call(bar))
// 答案
console.log(foo.logName.call(bar)) // mike

# 6.5 构造函数和this

function Foo() {
  this.bar = "range";
}

const instance = new Foo();
console.log(instance.bar);
// 答案
console.log(instance.bar); // range
function Foo() {
  this.user = "range";
  const obj = {}
  return obj
}

const instance = new Foo();
console.log(instance.user);
// 答案
console.log(instance.user); // undefined
// 此时instance返回的是空对象 obj
function Foo() {
  this.user = "range";
  return 1
}

const instance = new Foo();
console.log(instance.user);
// 答案
console.log(instance.user); // range

如果构造函数中显式返回一个值,且返回的是一个对象,那么 this 就指向这个返回的对象;如果返回的不是一个对象,那么 this 仍然指向实例。

# 6.6 箭头函数中的this

箭头函数内部的this是词法作用域,由父上下文确定。

const foo = {
  fn: function() {
    setTimeout(function() {
      console.log(this);
    })
  }
}
console.log(foo.fn());
// 答案
console.log(foo.fn()); // window
// 因为this出现在setTimeout()的匿名函数里,因此this指向window
const foo = {
  fn: function() {
    setTimeout(() => {
      console.log(this);
    })
  }
}
console.log(foo.fn());
// 答案
console.log(foo.fn()); // {fn: f}
var x = 11;
var obj = {
  x: 22,
  say: () => {
    console.log(this.x);
  }
}

obj.say()
// 答案
obj.say() // 11
// 箭头函数中的 this
var a = 11;
function foo() {
  this.a = 22;
  let b = function() {
    console.log(this.a);
  };
  b();
}

var x = new foo();
// 答案
var x = new foo(); // 11
/*
在执行这行代码时,先 new,进入到 foo函数中
执行函数 b(),此时 this 指向全局对象 window 
所以输出 11.
但是如果打印 x 
则得到对象 { a: 22}
var a = 11;
function foo() {
  this.a = 22;
  let b = () => {
    console.log(this.a);
  }
  b();
}

var x = new foo();
// 答案
var x = new foo(); // 22
// 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。
// this 继承父执行上下文中的this

# 6.7 this优先级相关

function foo(a) {
  console.log(this.a)
}

const obj1 = {
  a: 1,
  foo: foo
}

const obj2 = {
  a: 2,
  foo: foo
}

obj1.foo.call(obj2);
obj2.foo.call(obj1);
// 答案
obj1.foo.call(obj2);  // 2 
obj2.foo.call(obj1);  // 1
//  call、apply的显示绑定一般来说优先级比隐式绑定更高
function foo(a) {
  this.a = a;
}

const obj1 = {};
var bar = foo.bind(obj1);
var baz = new bar(3);

bar(2);

console.log(obj1.a);
console.log(baz.a);
// 答案
console.log(obj1.a); // 2
// 通过 bind 将 bar 函数中的 this 绑定为 obj1 对象。
// 执行 bar(2) 后,obj1.a 的值为 2。
// 也就是说
// 执行 bar(2) 后,obj1 为 {a: 2}。

console.log(baz.a) // 3
// 尽管 bar 函数本身是通过 bind 构造的新函数
// 其内部已经将 this 绑定为 obj1
// 但是再次作为构造函数 构造 baz 的时候,返回的实例与 obj1 解绑
// 即,new 绑定修改了 bind 绑定的 this
// 所以, new 的优先级 高于 bind
function foo() {
  return a => {
    console.log(this.a);
  };
}

const obj1 = {
  a: 2
}

const obj2 = {
  a: 3
}

const bar = foo.call(obj1);

console.log(bar.call(obj2));
// 答案
console.log(bar.call(obj2)); // 2
// 由于 foo() 的this 绑定到了 obj1
// bar(引用箭头函数) 的 this 也会绑定到 obj1
// 箭头函数的绑定无法被修改
var a = 123;

const foo = () => a => {
  console.log(this.a);
}

// 上面的相当于
/*
var foo = function foo() {
  return function (a) {
    console.log(_this.a);
  };
};
*/

const obj1 = {
  a: 2;
}

const obj2 = {
  a: 3;
}

var bar = foo.call(obj1);

console.log(bar.call(obj2));
// 答案
console.log(bar.call(obj2)); // 123
const a = 123;
const foo = () => a => {
  console.log(this.a);
}

const obj1 = {
  a: 2;
}

const obj2 = {
  a: 3;
}

var bar = foo.call(obj1);

console.log(bar.call(obj2));
// 答案
console.log(bar.call(obj2)); // undefined
// let 和 const 声明的变量都不会挂载到 window 全局对象中。

# 6.8 闭包/嵌套中的this

嵌套函数(闭包)不会从调用它的函数中继承this。若想访问外部函数的this值,需要将它的值保存在一个变量里,这个变量和内部函数都同在一个作用域内。

// 普通的嵌套
var name = "sirius";
let foo = {
  name: "range",
  foo1: function() {
    console.log("foo1: ", this.name);
    function foo2(){
      console.log("foo2: ", this.name);
      function foo3() {
        console.log("foo3: ", this.name);
      }
      foo3()
    }
    foo2();
  }
}

foo.foo1();
// 答案
foo.foo1();
// foo1: range
// foo2: sirius
// foo3: sirius

/*
这里,foo 对象调用了它内部的函数 foo1,foo1是被对象 foo 调用,所以才会打印出 foo1:range。
嵌套函数 foo2 不是被对象 foo 调用,而是被函数 foo1 调用。
但是嵌套函数 foo2 不会从调用它的函数 foo1 中继承 this。
因此 foo2 的 this 指向的是 undefined。
所以 foo2 的 this 指向全局对象 window(严格模式下为 undefined)
*/

但是,即使是在闭包中,箭头函数的this也是声明时绑定的。

// 多层嵌套箭头函数
var name = "sirius";
let foo = {
  name: "range",
  // 第一层箭头函数
  foo0: () => {
    console.log("arrow1: ", this.name);
    // 第二层箭头函数
    (() => {
      console.log("arrow2: ", this.name);
      // 第三层箭头函数
      (() => {
        console.log("arrow3: ", this.name);
      })()
    })()
  }
};

foo.foo0();
// 答案

foo.foo0();
// arrow1: sirius
// arrow2: sirius
// arrow2: sirius

// 因为对象不构成单独的作用域
// 所以 foo0 箭头函数被调用时的作用域,就是全局作用域
// 多层嵌套箭头函数
var name = "sirius";
let foo = {
  name: "range",
  // 第一层普通函数
  foo0: function() {
    console.log("foo0: ", this.name);
    // 第二层箭头函数
    (() => {
      console.log("arrow1: ", this.name);
      // 第三层箭头函数
      (() => {
        console.log("arrow2: ", this.name);
      })()
    })()
  }
};

foo.foo0();
// 答案

foo.foo0();
// arrow1: range
// arrow2: range
// arrow2: range

// 虽然对象不构成单独的作用域,但是函数有作用域
// 所以后面所有的箭头函数的 this,都被阻挡在了函数作用域内
// 因此,箭头函数的 this 都是第一层函数的this,
// 多层嵌套箭头函数
var name = "sirius";
let foo = {
  name: "range",
  // 第一层普通函数
  foo0: function () {
    console.log("foo0: ", this.name);
    // 第二层普通函数
    function foo1() {
      console.log("foo1: ", this.name);
      // 第三层箭头函数
      (() => {
        console.log("arrow1: ", this.name);
        // 第四层箭头函数
        (() => {
          console.log("arrow2: ", this.name);
        })()
      })()
    }
    foo1();
  }
};

foo.foo0();
// 答案

foo.foo0();
// foo0: range
// foo1: sirius
// arrow1: sirius
// arrow2: sirius

// 还是因为函数有单独的作用域
// foo0 的作用域内,又形成了一个小的 foo1 的作用域
// foo1 的作用域 阻挡了内部的箭头函数访问 foo0 的作用域
// 所以箭头函数都指向 foo1 的 this
// foo1 的 this 就是 undefined
// 下面的代码就是多余的了,当练习吧
// 代码是键盘的体操
// 不同的嵌套方式
var name = "sirius";
let foo = {
  name: "range",
  // 1.箭头函数 嵌套 普通函数
  foo0: () => {
    console.log("arrow1: ", this.name);
    function foo1() {
      console.log("foo1: ", this.name);
    }
    foo1();
  },
  // 2.1 普通函数 嵌套 箭头函数
  foo2: function() {
    console.log("foo2: ", this.name);
    (() => {
      console.log("arrow2: ", this.name);
    })()
  },
  // 2.2 箭头函数 嵌套 箭头函数
  foo3: () => {
    console.log("arrow3: ", this.name);
    (() => {
      console.log("arrow4: ", this.name);
    })()
  }
}

foo.foo0()
foo.foo2()
foo.foo3()
// 答案

foo.foo0() 
// arrow1: sirius,因为对象不构成单独的作用域
// foo1: sirius,因为普通嵌套函数的 this 指向全局对象

foo.foo2()
// foo2: range,因为是 foo 对象调用该函数
// arrow2: range
// 因为函数 foo2 构成了单独的作用域,而箭头函数没有作用域
// 所以箭头函数内外的 this 都是一样的
// 即 箭头函数的 this 就是包含这个箭头函数的函数 foo2 的 this

foo.foo3()
// arrow3: sirius
// arrow4: sirius 即使多层嵌套,箭头函数都是指向外面的(缺少屏障隔开)