Code Monkey home page Code Monkey logo

mini-vue's Introduction

感谢关注 Thanks for your attention ༼ つ ◕_◕ ༽つ

☀️ 个人优势

  • 丰富的多端 0-1 前后端开发经验,大型前后端开发重构经验,有 4-5 前端团队搭建敏捷项目管理,团队多人协同开发能力,可独立全栈开发
  • 熟练前端架构:qiankun微前端架构系统、vue3、taro、react、express/node、vite3、TypeScript/4、Ant-Design-Vue/3.2、element-plus/2.2、vue-i18n/9、mitt/3、axios、pinia/2、vue-router/4、tailwindcss/3、VueRequest、VueUse等主流技术开发,跨平台uniapp、flutter框架,移动端H5网页、PC、微信/支付宝 小程序、安卓/IOS App等。
  • 熟练后端架构:微服务、Maven 多模块管理、Java8、Spring、Spring Boot2、SpringMVC、Docker、RabbitMQ、kafka、Cassandra、Elasticsearch、Nacos(Nacos Config)服务注册中与配置中心、Ribbon负载均衡、OpenFeign服务调用、sentinel服务熔断、SpringCloud Gateway服务网关、SpringCloud、SpringCloud Alibaba、MyBatis、Mybatis-Plus、log4j、Nginx(负载均衡)、SpringSecurity、Redis、MySql、MongoDB、Zookeeper、Seata分布式事务、Skywalking链路追踪理等。
  • 熟练运维方面:熟悉Linux操作系统及常用命令,了解Docker,k8s等,Linux日常服务器操作管理。
  • 管理层经验4+年:团队技术选型、技术攻坚及管理工作,注重前端标准化,模块化,工程化,在部门内部标准落地,已落地电商、新能源、物联网、有声小说行业业务能力。
  • 微服务平台稳定生产了三年+,mysql主从模式,使用Netty、mqtt采集设备信息,k8s + jenkins的部署架构,采用Spring Boot 2.7 、Spring Cloud 2021 ,集成Minio分布式对象存储、Skywalking追踪监控、Sentinel从流量控制、熔断降级、系统负载等多个维度保护服务的稳定性,注册中心、配置中心选型Nacos,使用Traefik进行反向代理,借鉴OAuth2,实现了多终端认证系统,可控制子系统的token权限互相隔离,借鉴Security,封装了Secure模块,采用JWT做Token认证,可拓展集成Redis等细颗粒度控制方案,完全遵循阿里巴巴编码规范,实现SaaS多租户微服务平台。
  • 深入理解 Vue,并研究过其内部实现,并输出 JS LeetCode 算法解决方案(小册),刷题近500道题。
  • 喜欢分享,推动团队内部技术分享与复盘总结,工作认真负责,注重代码质量,工作效率。

汇总

我喜欢交朋友。所以如果你想打个招呼,我会很高兴见到你更多! 😊

  • 💬 我的微信号: xiaoda0423⚡ 👉 如果你有问题提出: click
  • 🤔 坚持的趣事: 终身学习 common Snippets 坚持运动,阅读
  • JavaGuideInterview学习 每日 3+1,以面试题来驱动学习,提倡每日学习与思考,每天进步一点!每天早上纯手工发布(死磕自己,愉悦大家)准备 Java 面试,首选 JavaGuideInterview!面试题大收集,面试集锦 ❤ 💝 💘
  • WebGuideInterview学习 每日 3+1,以面试题来驱动学习,提倡每日学习与思考,每天进步一点!每天早上纯手工发布(死磕自己,愉悦大家)准备 前端 面试,首选 WebGuideInterview!面试题大收集,面试集锦 ❤ 💝 💘

开源

📚 小书

封面 书名 简述 成就
《Webpack学习笔记》 《Webpack学习笔记》 Webpack学习笔记 badge badge
《前端进阶JavaScript标准库》 《前端进阶JavaScript》 前端进阶 JavaScript 标准库 badge badge
《腾讯精选练习 50 题》 《腾讯精选练习 50 题》 力扣 (LeetCode) 🐧 腾讯精选练习 50 题 badge badge
《Bytedance-campus-59-Leetcode》 《字节校园 59》 力扣 (LeetCode) 🐿️ 字节校园 算法与数据结构 badge badge
《LeetCode-HOT-100》 《LeetCode HOT 100》 力扣 (LeetCode) 🔥LeetCode HOT 100 badge badge
《数据结构与算法概念与实现》 《数据结构与算法》 哪吒的数据结构与算法 badge badge
《场景代码题-API实现原理-实战题目》 《手写代码》 ✨✨✨ 集锦 前端JavaScript 手写题,编程题,Not just for interviews badge badge
项目 Github 简述 技术 成就
【uui】 Github 🖖 uui组件库 badge badge badge badge badge
【file-breakpoint-continue】 Github 🐬 实现大文件上传,断点续传等 badge badge badge badge
【express-node】 Github 🌙 express-node-mysql-react badge badge badge
【awesome-css】 Github 🍉 国内css平台从业者交流 badge badge badge badge
【nice-my-friend】 Github ♻️ 关注人数列表数据 badge badge badge
【awesome-stars-webVueBlog】 Github 🤩 我的star列表 badge badge badge
【promise】 Github 🐧 Promises/A+ 实现 badge badge badge
【mini-vue】 Github 🖖 mini-vue badge badge badge
数据总览 常用开发语言

mini-vue's People

Contributors

huangguangda avatar webvueblog avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

mini-vue's Issues

3.手写柯里化函数

在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

function add(a, b) {
    return a + b;
}

// 执行 add 函数,一次传入两个参数即可
add(1, 2) // 3

// 假设有一个 curry 函数可以做到柯里化
var addCurry = curry(add);
addCurry(1)(2) // 3
const multiArgFunction = (a, b, c) => a + b + c;
console.log(multiArgFunction(1, 2, 3)); // 6

const curryUnaryFunction = (a) => (b) => (c) => a + b + c;
curryUnaryFunction(1); // returns a function: b => c =>  1 + b + c
curryUnaryFunction(1)(2); // returns a function: c => 3 + c
curryUnaryFunction(1)(2)(3); // returns the number 6

手写柯里化函数

// 手写
function curry(fn, ...args) {
 // fn原函数
 // ...args可以传入初始参数
 // 返回一个函数

 return function() {
  // 缓存目前接收到的参数
  let _args = [...args, ...arguments];

  // 原函数应该接收的参数个数
  let len = fn.length;

  // 比较目前的参数累计与原函数应该接收的参数
  if(_args.length < len) {
   // 代表需要继续返回一个新函数
   // 使用递归,形成闭包,保证函数的独立,不受影响
   return curry(fn, ..._args);
  } else { 
   // 参数累计够了,执行原函数返回结果
   return fn.apply(this, _args);
  }
 }
}

干净版本:

function curry(fn, ...args) {
 const { length } = fn

 return function() {
  const _args = [...args, ...arguments]
  
  if (_args.length < length) return curry(fn, ..._args)

  return fn.apply(this, _args)
 }
}

es6实现:

const curry = (fn, ...args) =>
 fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);

带占位的封装

function curry(fn, args = [], holes = []) {
  const { length } = fn;return function (...args2) {
    const _args = args.slice(0); //存放组合后的参数
    const _holes = holes.slice(0);
    const argsLen = args.length; //fn的实参的长度
    const holesLen = holes.length;
    let index = 0;for (let i = 0; i < args2.length; i++) {
      let arg = args2[i];

      // 处理类似 fn(1, _, _, 4)(_, 3) 这种情况,index 需要指向 holes 正确的下标
      if (arg === _ && holesLen) {
        index++;
        if (index > holesLen) {
          _args.push(arg);
          _holes.push(argsLen - 1 + index - holesLen);
        }
      }

      // 处理类似 fn(1)(_) 这种情况
      else if (arg === _) {
        _args.push(arg);
        _holes.push(argsLen + i);
      }

      // 处理类似 fn(_, 2)(1) 这种情况
      else if (holesLen) {
        // fn(_, 2)(_, 3)
        if (index >= holesLen) _args.push(arg);

        // fn(_, 2)(1) 用参数 1 替换占位符
        else {
          _args.splice(_holes[index], 1, arg);
          _holes.splice(index, 1);
        }
      }

      else _args.push(arg);}if (_holes.length || _args.length < length) return curry.call(this, fn, _args, _holes);

    return fn.apply(this, _args);
  }
}

// test
const _ = {};

const fn = curry(function(a, b, c, d, e) {
    console.log([a, b, c, d, e]);
});

// 验证 输出全部都是 [1, 2, 3, 4, 5]
fn(1, 2, 3, 4, 5);
fn(_, 2, 3, 4, 5)(1);
fn(1, _, 3, 4, 5)(2);
fn(1, _, 3)(_, 4)(2)(5);
fn(1, _, _, 4)(_, 3)(2)(5);
fn(_, 2)(_, _, 4)(1)(3)(5);

柯里化:将使用多个参数的函数转化为一系列使用一个参数的函数

作用:延迟计算,参数复用,提高通用型和适配性

function curry(fn) {
 let length = fn.length;
 let curArg = Array.prototype.slice.call(arguments, 1) || [];
 
 return function() {
  let fnArg = Array.prototype.slice.call(arguments);
  let currArg = currArg.concat(fnArg);
  // 如果现在的参数小于所需参数的话,说明还不够,继续使用柯里化的函数,处理
  if (currArg.length < length) {
   return curry(fn, ...currArg);
  } else {
   return fn.apply(this, currArg);
  }
 };
}
function CurryHelper(fn: Function, ...args: any[]) {
  // 返回一个 包裹执行函数的函数;等待参数满后被调用
  return function (...innerArgs: any[]) {
    // 执行被柯里化的函数
    return fn.apply(this, [...args, ...innerArgs]);
  }
}

function Curry(fn: Function, length?: number) {
  length = length || fn.length

  return function (...args: any[]) {
    if (args.length < length) {
      // 收集 function 和 参数
      const combined = [fn, ...args]
      // 收集 剩余参数长度
      const residueLen = length - args.length
      // 收集 待执行的函数
      const CurryHelperReturn = CurryHelper.apply(this, combined)

      // 参数未满,继续被调用。
      return Curry(CurryHelperReturn, residueLen)
    } else {

      // 参数已满,执行函数
      return fn.apply(this, args)
    }
  }
}

export {
  Curry
}

羽哥:

// 第三版
function curry(fn, args, holes) {
    length = fn.length;

    args = args || [];

    holes = holes || [];

    return function() {

        var _args = args.slice(0),
            _holes = holes.slice(0),
            argsLen = args.length,
            holesLen = holes.length,
            arg, i, index = 0;

        for (i = 0; i < arguments.length; i++) {
            arg = arguments[i];
            // 处理类似 fn(1, _, _, 4)(_, 3) 这种情况,index 需要指向 holes 正确的下标
            if (arg === _ && holesLen) {
                index++
                if (index > holesLen) {
                    _args.push(arg);
                    _holes.push(argsLen - 1 + index - holesLen)
                }
            }
            // 处理类似 fn(1)(_) 这种情况
            else if (arg === _) {
                _args.push(arg);
                _holes.push(argsLen + i);
            }
            // 处理类似 fn(_, 2)(1) 这种情况
            else if (holesLen) {
                // fn(_, 2)(_, 3)
                if (index >= holesLen) {
                    _args.push(arg);
                }
                // fn(_, 2)(1) 用参数 1 替换占位符
                else {
                    _args.splice(_holes[index], 1, arg);
                    _holes.splice(index, 1)
                }
            }
            else {
                _args.push(arg);
            }

        }
        if (_holes.length || _args.length < length) {
            return curry.call(this, fn, _args, _holes);
        }
        else {
            return fn.apply(this, _args);
        }
    }
}

var _ = {};

var fn = curry(function(a, b, c, d, e) {
    console.log([a, b, c, d, e]);
});

// 验证 输出全部都是 [1, 2, 3, 4, 5]
fn(1, 2, 3, 4, 5);
fn(_, 2, 3, 4, 5)(1);
fn(1, _, 3, 4, 5)(2);
fn(1, _, 3)(_, 4)(2)(5);
fn(1, _, _, 4)(_, 3)(2)(5);
fn(_, 2)(_, _, 4)(1)(3)(5)

高颜值写法:

var curry = fn =>
    judge = (...args) =>
        args.length === fn.length
            ? fn(...args)
            : (arg) => judge(...args, arg)

【数据结构分类】二叉树的基本操作

完全二叉树的一些公式

截屏2022-05-17 06 57 01

基本结构

插入,遍历,深度

function Node(data, left, right) {
 this.data = data;
 this.left = left;
 this.right = right;
}
Node.prototype = {
 show: function() {
  console.log(this.data);
 }
}
function Tree = {
 this.root = null;
}
Tree.prototype = {
    insert: function (data) {
        var node = new Node(data, null, null);
        if (!this.root) {
            this.root = node;
            return;
        }
        var current = this.root;
        var parent = null;
        while (current) {
            parent = current;
            if (data < parent.data) {
                current = current.left;
                if (!current) {
                    parent.left = node;
                    return;
                }
            } else {
                current = current.right;
                if (!current) {
                    parent.right = node;
                    return;
                }
            }

        }
    },
    preOrder: function (node) {
        if (node) {
            node.show();
            this.preOrder(node.left);
            this.preOrder(node.right);
        }
    },
    middleOrder: function (node) {
        if (node) {
            this.middleOrder(node.left);
            node.show();
            this.middleOrder(node.right);
        }
    },
    laterOrder: function (node) {
        if (node) {
            this.laterOrder(node.left);
            this.laterOrder(node.right);
            node.show();
        }
    },
    getMin: function () {
        var current = this.root;
        while(current){
            if(!current.left){
                return current;
            }
            current = current.left;
        }
    },
    getMax: function () {
        var current = this.root;
        while(current){
            if(!current.right){
                return current;
            }
            current = current.right;
        }
    },
    getDeep: function (node,deep) {
        deep = deep || 0;
        if(node == null){
            return deep;
        }
        deep++;
        var dleft = this.getDeep(node.left,deep);
        var dright = this.getDeep(node.right,deep);
        return Math.max(dleft,dright);
    }
}
var t = new Tree();
t.insert(3);
t.insert(8);
t.insert(1);
t.insert(2);
t.insert(5);
t.insert(7);
t.insert(6);
t.insert(0);
console.log(t);
// t.middleOrder(t.root);
console.log(t.getMin(), t.getMax());
console.log(t.getDeep(t.root, 0));
console.log(t.getNode(5,t.root));

树查找

getNode: function (data, node) {
    if (node) {
        if (data === node.data) {
            return node;
        } else if (data < node.data) {
            return this.getNode(data,node.left);
        } else {
            return this.getNode(data,node.right);
        }
    } else {
        return null;
    }
}

利用二分的**

二分查找

二分查找的条件是必须是有序的线性表。

和线性表的中点值进行比较,如果小就继续在小的序列中查找,如此递归直到找到相同的值。

function binarySearch(data, arr, start, end) {
  if (start > end) {
      return -1;
  }
  var mid = Math.floor((end + start) / 2);
  if (data == arr[mid]) {
      return mid;
  } else if (data < arr[mid]) {
      return binarySearch(data, arr, start, mid - 1);
  } else {
      return binarySearch(data, arr, mid + 1, end);
  }
}
var arr = [0, 1, 1, 1, 1, 1, 4, 6, 7, 8]
console.log(binarySearch(1, arr, 0, arr.length-1));

8.实现object.create

Object.create = function(o) {
 function f() {};
 f.prototype = o;
 return new f;
}

Object.create() 方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

const _create = (proto, propertiesObject, isNeedSupportSecondParam = false) => {
  //参数校验
  if (typeof proto !== "object" && proto !== null)
    throw new Error("the first param must be an object or null");

  if (isNeedSupportSecondParam && propertiesObject === null) {
    throw "TypeError";
  }

  function F() {}
  F.prototype = proto;
  const obj = new F();
  if (proto === null) {
    obj.__proto__ = proto;
  }

  if (isNeedSupportSecondParam && propertiesObject !== null) {
    Object.defineProperties(obj, propertiesObject);
  }

  return obj;
};

Object.create(proto,[propertiesObject])

proto:新创建对象的原型对象。

propertiesObject:可选。需要传入一个对象,该对象的属性类型参照Object.defineProperties()的第二个参数。如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。

Object.mycreate = function(proto,propertiesObject){
    if(typeof(proto) !== 'object' && proto !== null) throw new Error("the first param must be an object or null");
    function Fn(){}
    Fn.prototype = proto
    if (propertiesObject) Object.defineProperties(obj, propertiesObject);
    return new Fn
}
const create = (prop, props) => {
  if (!['object', 'function'].includes(typeof prop)) {
    throw new TypeError(
      `Object prototype my only be an Object or null: ${prop}`
    )
  }

  const Ctor = function () {} // 创建构造函数

  Ctor.prototype = prop // 赋值原型

  const obj = new Ctor() // 创建实例

  if (props) Object.defineProperties(obj, props) // 支持第二个参数

  if (prop === null) obj.__proto__ = null // 支持空原型

  return obj
}

9.实现 object.assign

Object.assign(target, ...source)
// target目标对象
// ...source源对象
// 可以是一个或多个,返回修改后的目标对象。

浅拷贝Object.assign

主要是将所有可枚举属性的值从一个或多个源对象中复制到目标对象,同时返回目标对象。语法:

Object.assign(target, ...source);

其中target是目标对象,...source是源对象,可以是一个或多个,返回修改后的目标对象。如果目标对象和源对象具有相同属性,则目标对象的该属性将会被源对象的相同属性覆盖,后来的源对象的属性将会类似地覆盖早先的属性。

浅拷贝就是拷贝对象的第一层的基本类型值,以及第一层的引用类型地址。

// 第一步:
let a = {
 name: 'da',
 age: 18
}

let b = {
 name: 'dada',
 book: {
  title: 'it',
  price: '15'
 }
}
let c = Object.assign(a, b);
console.log(c);

// {
//   name: 'dada',
//.  age: 18,
//.  book: {title: 'it', price: '15'}
// }
console.log(a === c); // true

String类型和Symbol类型的属性都会被拷贝,而且不会跳过那些值为null或undefined的属性。

let a = {
   name: "Jane",
   age: 20
}
let b = {
   b1: Symbol("Jane"),
   b2: null,
   b3: undefined
}
let c = Object.assign(a, b);
console.log(c);
// {
//     name: "Jane",
//     age: 20,
//     b1: Symbol(Jane),
//     b2: null,
//     b3: undefined
// } 
console.log(a === c); // true

Object.assign模拟实现

1、判断原生Object是否支持该函数,如果不存在的话创建一个函数assign,并使用Object.defineProperty将该函数绑定到Object上。

2、判断参数是否正确(目标对象不能为空,我们可以直接设置{}传递进去,但必须设置值)。

3、使用Object()转成对象,并保存为to,最后返回这个对象to。

4、使用for...in循环遍历出所有可枚举的自有属性。并复制给新的目标对象(hasOwnProperty返回非原型链上的属性)。

为了方便验证方便,使用assign2代替assign,注意以下模拟实现不支持symbol属性,因为ES5中根本没有symbol,实现代码如下:

if (typeof Object.assign2 != 'function') {
   Object.defineProperty(Object, 'assign2', { // 注意点1
       value: function(target) {
           'use strict';
           if (target == null) { // 注意点2
               throw new Error('Cannot convert undefined or null to object');
           }
           var to = Object(target); // 注意点3
           for (var index = 1; index < arguments.length; index++) {
               var nextSource = arguments[index];
               if (nextSource != null) { // 注意点2
                   // 注意点4
                   for (var nextKey in nextSource) {
                       if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                           to[nextKey] = nextSource[nextKey];
                       }
                   }
               }
           }
           return to;
       },
       writable: true,
       configurable: true
   })
}
Object.getOwnPropertyDescriptor(Object, 'assign');
// {
//     value: ƒ, 
//     writable: true,     // 可写
//     enumerable: false,  // 不可枚举,注意这里是 false
//     configurable: true  // 可配置
// }
Object.propertyIsEnumerable('assign'); // false
Object.defineProperty(Object, 'assign', {
  value: function(target, ...args) {
    if (target == null) {
      return new TypeError('Cannot convert undefined or null to object');
    }
    
    // 目标对象需要统一是引用数据类型,若不是会自动转换
    const to = Object(target);

    for (let i = 0; i < args.length; i++) {
      // 每一个源对象
      const nextSource = args[i];
      if (nextSource !== null) {
        // 使用for...in和hasOwnProperty双重判断,确保只拿到本身的属性、方法(不包含继承的)
        for (const nextKey in nextSource) {
          if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
    }
    return to;
  },
  // 不可枚举
  enumerable: false,
  writable: true,
  configurable: true,
})

1.手写call和apply

call的场景是什么呢?每次写一个api,肯定得先知道他的用途才可以。

一句话介绍call:call()方法在使用 一个指定的this值 和 “若干个”指定的参数值 的前提下调用某个函数或方法。

var foo = {
 value: 'dada'
};

function bar() {
 console.log(this.value);
};

bar.call(foo); // dada;
bar(foo); // undefined;

注意两点:

  1. call改变了this的指向,指向到foo
  2. bar函数执行了

模拟一下:

思路:

  1. 将函数设为对象的属性
  2. 执行该函数
  3. 删除该函数
// call()手写
Function.prototype.myCall = function(context) {
 // 判断参数context 如果不存在 则为window;
 context = context || window;

 // 赋值函数 this
 context.func = this;

 // 获取参数
 let args = [...arguments].slice(1);

 // 获取结果
 let result = context.func(...args);

 // 删除属性
 delete context.func;

 // 返回结果
 return result;
}
// 手写apply
Function.prototype.myApply = function(context) {
 context = context || window;
 context.func = this;

 let result
 if(arguments[1]) {
  result = context.func(...arguments[1]);
 } else {
  result = context.func();
 }
 
 delete context.func;
 return result;
}
// 手写apply
Function.prototype.myApply = function(context, args) {
 context = context || window;
 context.func = this;

 let result = null;
 if(args.length === 0) {
  result = context.func();
 } else {
  result = context.func(...args);
 }

 delete context.func;
 return result;
}

call的实现:

Function.prototype.myCall = function(context, ...arguments) {
 context = context || window; // call不传值默认为window
 context.func = this;
 const result = context.func(...arguments);
 delete context.func;
 return result;
};

apply的实现:

Function.prototype.myApply = function(context, ...arguments) {
 context = context || window;
 context.func = this;
 arguments = arguments || [];
 let result = context.func(...arguments);
 delete context.fn;
 return result;
}

call和apply的使用

  1. 函数原型上的方法
  2. call,apply第一个参数都是上下文 改变this指向
  3. call第二个参数开始,接受数组展开项目
  4. apply第二个参数开始,接受一个数组
  5. 返回值:返回 调用函数的返回值
interface ThisArg {
 fn: Function
}

declare global {
 interface Winodw {
  fn: Function
 }
}

function Call(thisArg: ThisArg, ...args: any[]) {
 const context = thisArg || window
 context.fn = this
 const result = thisArg.fn(...args)
 delete thisArg.fn
 return result
}

function Apply(thisArg: ThisArg, args: any[]) {
 const context = thisArg || window
 context.fn = this

 let result
 
 if(!args.length) {
  result = context.fn()
 } else {
  result = contextfn(...args)
 }
 
 delete thisArg.fn
 return result
}

实现**,通过 object 中函数 可以改变 this 的指向

const obj = {
 value: 'dada',
 func: function() {
  console.log(this.value);
 }
}
obj.func(); // dada

默认值:

Function.prototype.myCall = function(context = window, ...args) {
 const fnKey = Symbol('fn');
 context[fnKey] = this;
 const result = context[fnKey](...args);
 delete context[fnKey];
 return result;
}
Function.prototype.myApply = function(context = window, args=[]) {
 const fnKey = Symbol('fn');
 context[fnKey] = this;
 const result = context[fnKey](...args);
 delete context[fnKey];
 return result;
}

简写版本的call和apply手写 💯

Function.prototype.myCall = function(context) {
 const context = Object(context) || window;
 context.fn = this;
 let args = [];
 for(let i = 1, len = arguments.length; i < len; i++) {
  args.push('arguments[' + i + ']');
 }
 var result = eval('context.fn(' + args + ')‘);
 delete context.fn;
 return result;
}
Function.prototype.myApply = function(context, arr) {
 const const = Object(context) || window;
 context.fn = this;
 let result;
 if(!arr) {
  result = context.fn();
 } else {
  let args = [];
  for(let i = 0, len = arr.length; i < len; i++) {
   args.push('arr[' + i + ']');
  }
  result = eval('context.fn(' + args + ')');
 }
 delete context.fn;
 return result;
}

【数据结构分类】二叉树-概览

image

什么是二叉树

树是用来模拟具有树状结构性质的数据集合。根据它的特性可以分为非常多的种类,对于我们来讲,掌握二叉树这种结构就足够了,它也是树最简单、应用最广泛的种类。

二叉树是一种典型的树树状结构。如它名字所描述的那样,二叉树是每个节点最多有两个子树的树结构,通常子树被称作“左子树”和“右子树”。

  1. 二叉树的基本操作(解答)

  2. 二叉树的前序遍历(解答)

  3. 二叉树的中序遍历(解答)

  4. 二叉树的后序遍历(解答)

请写出递归和非递归版本的,前中后序遍历。

题目

重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

求二叉树的遍历

给定一棵二叉树的前序遍历和中序遍历,求其后序遍历

输入描述:

两个字符串,其长度n均小于等于26。 第一行为前序遍历,第二行为中序遍历。 二叉树中的结点名称以大写字母表示:A,B,C....最多26个结点。

输出描述:

输入样例可能有多组,对于每组测试样例, 输出一行,为后序遍历的字符串。

样例:

输入
ABC
BAC
FDXEAG
XDEFAG

输出
BCA
XEDGAF

11.手写原型式继承、寄生式继承、寄生组合式继承

原型式继承

function object(o) {
 function F() {}
 F.prototype = o;
 return new F();
}

在函数内部,先创建 临时性的构造函数,然后将传入的对象作为这个 构造函数的原型,最后返回这个临时构造函数的一个实例。从本质上,该函数对传入的对象执行了一次 浅拷贝。

ECMAScript 5 通过增加 Object.create()方法

这个方法接收两个参数:作为新对象原型的对象,以及给新对象定义额外属性的对象(第二个可选)

let person = {
 name: "Nicholas",
 friends: ["Shelby", "Court", "Van"]
};
let anotherPerson = Object.create(person, {
 name: {
 value: "Greg"
 }
});
console.log(anotherPerson.name); // "Greg"

场景:

原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合

缺点:

属性中包含的引用值始终会在相关对象间共享,跟使用原型模式是一样的。(引用类型值的属性始终都会共享相应的值,多个实例对象对引用类型的操作会被篡改。)

原型式继承是借助原型基于已有的对象创建新对象,同时还不必因此创建自定义类型。

function Person(friendship) {
  function Creator() {}
  Creator.prototype = friendship;
  return new Creator();
}

寄生式继承

寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。

function createAnother(original){
 let clone = object(original); // 通过调用函数创建一个新对象
 clone.sayHi = function() { // 以某种方式增强这个对象
 console.log("hi");
 };
 return clone; // 返回这个对象
} 

场景:

寄生式继承同样适合主要关注对象,而不在乎类型和构造函数的场景。(在主要考虑对象而 不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。)

寄生式继承(Parasitic Inheritance):创建一个仅用于封装继承过程的函数,在函数内部以某种方式增强对象

function creator(origin) {
  // 以 origin 为原型对象创建一个新对象
  let clone = Object.create(origin);

  // 以某种方式来增强这个对象
  clone.sayHi = function () {
    console.log('Hello world!');
  };

  // 返回这个对象
  return clone;
}

⚠️ 注意: 使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一点与借用构造函数模式类似。

寄生式组合继承

寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
(寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法。)

组合继承的例子:

function SubType(name, age){
 SuperType.call(this, name); // 第二次调用 SuperType()
 this.age = age;
}
SubType.prototype = new SuperType(); // 第一次调用 SuperType()
function inheritPrototype(subType, superType) {
 let prototype = object(superType.prototype); // 创建对象
 prototype.constructor = subType; // 增强对象
 subType.prototype = prototype; // 赋值对象
}

不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。 本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

function inherit(child, parent) {
  // 创建对象
  let prototype = Object.create(parent.prototype);

  // 增强对象
  prototype.constructor = child;

  // 指定对象
  child.prototype = prototype;
}

这个函数接收两个参数:子类型构造函数 和 超类型构造函数。

寄生式组合继承可以算是引用类型继承的最佳模式。

5.手写一个偏函数

什么是元?元是指函数参数的个数,比如一个带有两个参数的函数被称为二元函数

function add(a, b) {
    return a + b;
}

// 执行 add 函数,一次传入两个参数即可
add(1, 2) // 3

// 假设有一个 partial 函数可以做到局部应用
var addOne = partial(add, 1);

addOne(2) // 3
const partial = (fn, ...restArgs) => {
 // 默认参数和新传递的参数进行组合,然后调用方法
 return (...args) => func(...restArgs, ...args);
};

柯里化是将一个多参数函数转换成多个单参数函数,也就是将一个n元函数转换成n个一元函数。

局部应用则是固定一个函数的一个或者多个参数,也就是将一个n元函数转换成一个 n - x 元函数。

// 偏函数是柯里化的子集
function addParti(fn, ...args) {
 return function(...newArgs) {
  return fn(...args, ...newArgs);
 }
}
const partial = function() {
 const fn = Array.prototype.shift.call(arguments);
 const arg1 = [...arguments];
 return function() {
  const arg2 = [...arg1, ...arguments];
  return fn.call(this, ...arg2);
 }
}
function Partial(fn: Function, ...args: any[]) {
 return (...rest: any[]) => fn(...args, ...rest);
}
function partial(func, ...restArgs) {
 return function(...args) {
  return func.call(this, ...restArgs, ...args);
 }
}

箭头函数:

function addParti(fn, ...args) {
 return (...newArgs) => fn(...args, ...newArgs);
}

6.27道this-27道Promise

27道this-45道Promise

参考:【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理)

参考:【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)

this的5种绑定方式:

  1. 默认绑定(非严格模式下 this 指向全局对象,严格模式下 this 会绑定到 undefined)
  2. 隐式绑定(当函数引用有 上下文对象 时,如 obj.foo() 的调用方式,那么 foo 内的 this 指向 obj)
  3. 显示绑定(通过 call() 或者 apply() 方法直接指定 this 的绑定对象,如 foo.call(obj))
  4. new绑定
  5. 箭头函数绑定(this的指向由外层作用域决定的)

1

默认绑定:

var a = 10;
function foo() {
 console.log(this.a);
}
foo(); // 10

相当于

window.a = 10;
function foo() {
 console.log(this.a);
}
window.foo();

2

严格模式下

"use strict";
var a = 10;
function foo() {
 // this1 undefined
 console.log('this1', this);
 // 10
 console.log(window.a);
 // this知道了值,那么这个就报错
 // TypeError: Cannot read properties of undefined (reading 'a')
 console.log(this.a)
}

// 这里先执行 window.foo打印 foo的函数 f foo() {...}
console.log(window.foo)

// this2 Window
console.log('this2', this);
foo(); // 执行foo()函数,在严格模式下

3

是因为改用 let 或者 const ,变量都不会绑定到 window 上的:

let a = 10;
const b = 20;

function foo() {
 // undefined
 console.log(this.a);
 // undefined
 console.log(this.b);
}

foo();
// undefined
console.log(window.a);

4

var a = 1
function foo() {
 var a = 2
 // foo()函数内的 this 指向的是window,因为window调用的foo
 // Window{...}
 console.log(this);
 // window下的a
 // 1
 console.log(this.a);
}
foo();

5

var a = 1;
function foo() {
 var a = 2;
 function inner () {
  // this.a  this 指向window
  // 1
  console.log(this.a);
 }
 // 函数内的函数, 看清楚调用
 inner();
}

foo();

6

function foo() {
 console.log(this.a);
}
var obj = { a: 1, foo }
var a = 2;
obj.foo(); // 1

var obj = {
 a: 1,
 foo: function() {
  console.log(this.a);
 }
}
var a = 2;
obj.foo();

7

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

var obj = { a: 1, foo };
var a = 2;
var foo2 = obj.foo;

// this 指向的是obj执行的时候,打印出来的是obj对象中的a
obj.foo(); // 1

// window下的a
foo2(); // 2

8

function foo () {
  console.log(this.a)
};
var obj = { a: 1, foo };
var a = 2;
var foo2 = obj.foo;
var obj2 = { a: 3, foo2: obj.foo }

// obj.foo()中的this指向调用者obj
obj.foo(); // 1

// 调用者window,使用foo()中的this指向window
foo2(); // 2

// obj.foo 调用者是 ojb2,使用的foo()中的this指向 obj2
obj2.foo2(); // 3

9

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

function doFoo (fn) {
  // obj.foo 函数内this发生了改变,指向了window
  console.log(this)
  fn() // this.a 指向 window 2
}

var obj = { a: 1, foo }
var a = 2

doFoo(obj.foo)

10

如果你把一个函数当成参数传递到另一个函数的时候,也会发生隐式丢失的问题,且与包裹着它的函数的this指向无关。在非严格模式下,会把该函数的this绑定到window上,严格模式下绑定到undefined。

因为doFoo本来就是obj2调用的 所以doFoo指向obj2 , 因为foo这个函数本来就是window的a就是 2

他只是把obj的foo传进了doFoo 是在doFoo函数内部执行

这是this中的例外情况 当作为参数被传递时 会发生隐式绑定丢失

image

第一红圈,是隐式绑定,因为doFoo作为obj2的属性被调用,所以第5行中的this指向obj2

第二个红圈,obj.foo作为参数被传递,此时foo中的this会丢失原有的隐式绑定,可以理解为foo作为一个单独的函数被调用,此时和obj脱离的关系

所以第6行等于wondow.foo了

倒数第二行,当把函数进行赋值传递的时候,变量会指向函数的引用,这个时候会发生隐式绑定的丢失,函数的调用会采用默认策略

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

function doFoo (fn) { // this.a
  console.log(this) // 指向obj2对象,{ a: 3, doFoo: f }
  // obj.foo() 打印this.a 为2,也就是window下的
  fn() // 2
}

var obj = { a: 1, foo }
var a = 2
var obj2 = { a: 3, doFoo }

// obj2.doFoo 调用函数,指向是obj2,因为是obj2调用它
// obj.foo this.a 
obj2.doFoo(obj.foo)


// 使用严格模式
"use strict"
function foo () {
  console.log(this.a)
}
function doFoo (fn) {
  console.log(this)
  fn()
}
var obj = { a: 1, foo }
var a = 2
var obj2 = { a: 3, doFoo }

obj2.doFoo(obj.foo)

{ a:3, doFoo: f }
Uncaught TypeError: Cannot read property 'a' of undefined

11

  1. 使用.call()或者.apply()的函数是会直接执行的
  2. bind()是创建一个新的函数,需要手动调用才会执行
  3. .call().apply()用法基本类似,不过call接收若干个参数,而apply接收的是一个数组
function foo () {
  console.log(this.a)
}
var obj = { a: 1 }
var a = 2

foo() // 2
foo.call(obj) // 1
foo.apply(obj) // 1
foo.bind(obj) // 并不会执行

call、apply、bind接收到的第一个参数是空或者null、undefined的话,则会忽略这个参数。

function foo () {
  console.log(this.a)
}
var a = 2
foo.call() // 2
foo.call(null) // 2
foo.call(undefined) // 2

12

谁调用的函数,函数内的this指向的就是谁。

对于setTimeout中的函数,这里存在隐式绑定的隐式丢失,也就是当我们将函数作为参数传递时会被隐式赋值,回调函数丢失this绑定,因此这时候setTimeout中的函数内的this是指向window的。

var obj1 = {
  a: 1
}
var obj2 = {
  a: 2,
  foo1: function () {
    console.log(this.a)
  },
  foo2: function () {
    setTimeout(function () {
      console.log(this) // Window{...}
      console.log(this.a) 3
    }, 0)
  }
}
var a = 3

obj2.foo1() // 2
obj2.foo2()

13

var obj1 = {
  a: 1
}
var obj2 = {
  a: 2,
  foo1: function () {
    console.log(this.a)
  },
  foo2: function () {
    setTimeout(function () {
      console.log(this) // { a: 1 }
      console.log(this.a) 
    }.call(obj1), 0)
  }
}
var a = 3
obj2.foo1() // 2
obj2.foo2() // 1

// 2
// { a: 1 }
// 1
// obj2.foo2.call(obj1)
// 这种写法的话,我改变的就是foo2函数内的this的指向了,但是我们知道,foo2函数内this的指向和setTimeout里函数的this是没有关系的,因为调用定时器的始终是window。

14

var obj1 = {
  a: 1
}
var obj2 = {
  a: 2,
  foo1: function () {
    console.log(this.a)
  },
  foo2: function () {
    // 调用inner函数的依然是window
    function inner () {
      console.log(this) // Window{...}
      console.log(this.a) // 3
    }
    inner()

  }
}
var a = 3

obj2.foo1() // 2
obj2.foo2() //

15

function foo () {
  console.log(this.a)
}
var obj = { a: 1 }
var a = 2

foo() // 2
foo.call(obj) // 1

// foo().call(obj)开始会执行foo()函数,打印出2
// 但是会对foo()函数的返回值执行.call(obj)操作,可是我们可以看到foo()函数的返回值是undefined,因此就会报错了。
foo().call(obj)

16

function foo () {
  console.log(this.a)
  return function () {
    console.log(this.a)
  }
}
var obj = { a: 1 }
var a = 2

// 第一个数字2自然是foo()输出的,虽然foo()函数也返回了一个匿名函数,但是并没有调用它呀,只有写成foo()(),这样才算是调用匿名函数。
foo() // 2

// 第二个数字1是foo.call(obj)输出的,由于.call()是紧跟着foo的,所以改变的是foo()内this的指向,并且.call()是会使函数立即执行的,因此打印出1,同理,它也没有调用返回的函数。
foo.call(obj) // 1

// 在执行完foo()之后,会返回一个匿名函数,并且后面使用了.call(obj)来改变这个匿名函数的this指向并调用了它,所以输出了1。
foo().call(obj) // 2 1

17

call是会直接执行函数的,bind是返回一个新函数,但不会执行。

function foo () {
  console.log(this.a)
  return function () {
    console.log(this.a)
  }
}
var obj = { a: 1 }
var a = 2

// foo()会执行没错,打印出了2。
foo()

// foo.bind(obj)却不会执行,它返回的是一个新函数。
foo.bind(obj)

// foo().bind(obj)只会执行前面的foo()函数,打印出2,.bind(obj)只是将foo()返回的匿名函数显式绑定this而已,并没有调用。
foo().bind(obj)

18

function foo () {
  console.log(this.a)
  return function () {
    console.log(this.a)
  }
}
var obj = { a: 1 }
var a = 2

foo.call(obj)() // 1 2

19

var obj = {
  a: 'obj',
  foo: function () {
    console.log('foo:', this.a)
    return function () {
      console.log('inner:', this.a)
    }
  }
}
var a = 'window'
var obj2 = { a: 'obj2' }

// obj.foo()自然是打印出foo: obj和inner: window
obj.foo()()

// obj.foo.(obj2)()其实也没啥可疑惑的了,打印出foo: obj2和inner: window
obj.foo.call(obj2)()

// 打印出foo: obj和inner: obj2
obj.foo().call(obj2)

20

var obj = {
  a: 1,
  foo: function (b) {
    b = b || this.a
    return function (c) {
      console.log(this.a + b + c)
    }
  }
}
var a = 2
var obj2 = { a: 3 }

// 6
obj.foo(a).call(obj2, 1)

// 将foo函数内的this指向了obj2  a: 3
// b开始是undefined的,但是又因为有一句b = b || this.a,使得b变为了3
// 调用匿名函数,且和这个匿名函数内的this应该是指向window的
// 6
obj.foo.call(obj2)(1)

21

function foo1 () {
  console.log(this.a)
}
var a = 1
var obj = {
  a: 2
}

var foo2 = function () {
  foo1.call(obj)
}

foo2() // 2

// 这里foo2函数内部的函数foo1我们使用call来显式绑定obj,就算后面再用call来绑定window也没有用了。
foo2.call(window) // 2

22

function foo1 (b) {
  console.log(`${this.a} + ${b}`)
  return this.a + b
}
var a = 1
var obj = {
  a: 2
}

var foo2 = function () {
  return foo1.call(obj, ...arguments)
}

var num = foo2(3)
console.log(num)


'2 + 3'
5

23

function foo (item) {
  console.log(item, this.a)
}
var obj = {
  a: 'obj'
}
var a = 'window'
var arr = [1, 2, 3]

// arr.forEach(foo, obj)
// arr.map(foo, obj)
arr.filter(function (i) {
  console.log(i, this.a)
  return i > 2
}, obj)

// 如果我们没有传递第二个参数obj的话,this.a打印出来的肯定就是window下的a了,但是传入了之后将obj显示绑定到第一个参数函数上。

1 "obj"
2 "obj"
3 "obj"
  1. this永远指向最后调用它的那个对象
  2. 匿名函数的this永远指向window
  3. apply 和 call 会直接执行,bind是创建新函数,需要手动调用
  4. forEach,map, filter函数的第二个参数也是能显式绑定this的

24

箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined。

箭头函数 它里面的this是由外层作用域来决定的,且指向函数定义时的this而非执行时。

var obj = {
  name: 'obj',
  foo1: () => {
    console.log(this.name)
  },
  foo2: function () {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  }
}
var name = 'window'
obj.foo1()
obj.foo2()()

// 'window'
// 'obj'
// 'obj'
var name = 'window'
var obj1 = {
	name: 'obj1',
	foo: function () {
		console.log(this.name)
	}
}

var obj2 = {
	name: 'obj2',
	foo: () => {
		console.log(this.name)
	}
}

// 不使用箭头函数的obj1.foo()是由obj1调用的,所以this.name为obj1。
obj1.foo()

// 使用箭头函数的obj2.foo()的外层作用域是window,所以this.name为window。
obj2.foo()

// 'obj1'
// 'window'

25

var name = 'window'
var obj1 = {
  name: 'obj1',
  foo1: function () {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  },
  foo2: () => {
    console.log(this.name)
    return function () {
      console.log(this.name)
    }
  }
}
var obj2 = {
  name: 'obj2'
}
obj1.foo1.call(obj2)() // 'obj2' 'obj2'
obj1.foo1().call(obj2) // 'obj1' 'obj1'
obj1.foo2.call(obj2)() // 'window' 'window'
obj1.foo2().call(obj2) // 'window' 'obj2'

26

function Foo (value) {
    this.value = value
}
Foo.prototype.getValue = () => console.log(this.value)

const foo1 = new Foo(1)
foo1.getValue() // undefined
const Foo = (value) => {
    this.value = value;
}
const foo1 = new Foo(1)
// 事实上直接就报错了 Uncaught TypeError: Foo is not a constructor
console.log(foo1);
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
    console.log(this === window); // => true
    this.innerHTML = 'Clicked button';
});

27

function foo() {
  console.log( this.a );
}
var a = 2;
(function(){
  "use strict";
  foo();
})();

// 使用了"use strict"开启严格模式会使得"use strict"以下代码的this为undefined,也就是这里的立即执行函数中的this是undefined。

// 但是调用foo()函数的依然是window,所以foo()中的this依旧是window,所以会打印出2

// 如果你是使用this.foo()调用的话,就会报错了,因为现在立即执行函数中的this是undefined

// 或者将"use strict"放到foo()函数里面,也会报错。

手写一个new实现

function create() {
 // 1.获取构造函数,并且删除 arguments 中的第一项
 var Con = [].shift.call(arguments);
 // 2.创建一个空对象并链接到构造函数的原型,使它能访问原型中的属性
 var obj = Object.create(Con.prototype);
 // 3.使用apply改变构造函数中this的指向实现继承,使obj能访问到构造函数中的属性
 var res = Con.apply(this, arguments);
 // 4.优先返回构造函数返回的对象
 return res instanceof Object ? res : obj;
}

手写一个call实现

ES3实现:

function fnFactory(context) {
 let unique_fn = "fn";
 while(context.hasOwnProperty(unique_fn)) {
  unique_fn = "fn" + Math.random();
 }
 return unique_fn;
}
Function.prototype.myCall = function(context) {
 context = (context !== null && context !== undefined) ? Object(context) : window;
 let args = [];
 for(let i = 1, len = arguments.length; i < len; i++) {
  args.push("arguments[" + i + "]");
 }

 let fn = fnFactory(context);
 context[fn] = this;
 let result = eval("context[fn](" + args + ")");
 delete context[fn];
 return result;
}

ES6实现:

Function.prototype.myCall = function(context) {
 context = (context !== null && context !== undefined) ? Object(context) : window;
 let fn = Symbol();
 context[fn] = this;
 
 let args = [...arguments].slice(1);
 let result = context[fn](...args);
 
 delete context[fn];
 return result;
}
function fnFactory(context) {
  var unique_fn = "fn";
  while (context.hasOwnProperty(unique_fn)) {
    unique_fn = "fn" + Math.random();
  }
  return unique_fn;
}
Function.prototype.call2 = function(context) {
  // 1. 若是传入的context是null或者undefined时指向window;
  // 2. 若是传入的是原始数据类型, 原生的call会调用 Object() 转换
  context = (context !== null && context !== undefined) ? Object(context) : window;
  // 3. 创建一个独一无二的fn函数的命名
  var fn = fnFactory(context);
  // 4. 这里的this就是指调用call的那个函数
  // 5. 将调用的这个函数赋值到context中, 这样之后执行context.fn的时候, fn里的this就是指向context了
  context[fn] = this;
  // 6. 定义一个数组用于放arguments的每一项的字符串: ['agruments[1]', 'arguments[2]']
  var args = [];
  // 7. 要从第1项开始, 第0项是context
  for (var i = 1, l = arguments.length; i < l; i++) {
    args.push("arguments[" + i + "]");
  }
  // 8. 使用eval()来执行fn并将args一个个传递进去
  var result = eval("context[fn](" + args + ")");
  // 9. 给context额外附件了一个属性fn, 所以用完之后需要删除
  delete context[fn];
  // 10. 函数fn可能会有返回值, 需要将其返回
  return result;
};

手写一个apply实现

ES3实现:

function fnFactory(context) {
  var unique_fn = "fn";
  while (context.hasOwnProperty(unique_fn)) {
    unique_fn = "fn" + Math.random();
  }
  return unique_fn;
}
Function.prototype.apply2 = function(context, arr) {
  context = context ? Object(context) : window;
  var fn = fnFactory(context);
  context[fn] = this;

  var result;
  if (!arr) {
    result = context[fn]();
  } else {
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) {
      args.push("arr[" + i + "]");
    }
    result = eval("context[fn](" + args + ")");
  }
  delete context[fn];
  return result;
};

ES6实现:

Function.prototype.apply3 = function(context, arr) {
  context = context ? Object(context) : window;
  let fn = Symbol();
  context[fn] = this;

  let result = arr ? context[fn](...arr) : context[fn]();
  delete context[fn];
  return result;
};
function fnFactory(context) {
  var unique_fn = "fn";
  while (context.hasOwnProperty(unique_fn)) {
    unique_fn = "fn" + Math.random();
  }
  return unique_fn;
}
Function.prototype.apply2 = function(context, arr) {
  // 1. 若是传入的context是null或者undefined时指向window;
  // 2. 若是传入的是原始数据类型, 原生的call会调用 Object() 转换
  context = context ? Object(context) : window;
  // 3. 创建一个独一无二的fn函数的命名
  var fn = fnFactory(context);
  // 4. 这里的this就是指调用call的那个函数
  // 5. 将调用的这个函数赋值到context中, 这样之后执行context.fn的时候, fn里的this就是指向context了
  context[fn] = this;

  var result;
  // 6. 判断有没有第二个参数
  if (!arr) {
    result = context[fn]();
  } else {
    // 7. 有的话则用args放每一项的字符串: ['arr[0]', 'arr[1]']
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) {
      args.push("arr[" + i + "]");
    }
    // 8. 使用eval()来执行fn并将args一个个传递进去
    result = eval("context[fn](" + args + ")");
  }
  // 9. 给context额外附件了一个属性fn, 所以用完之后需要删除
  delete context[fn];
  // 10. 函数fn可能会有返回值, 需要将其返回
  return result;
};

手写一个bind实现

  1. 函数内的this表示的就是调用的函数
  2. 可以将上下文传递进去, 并修改this的指向
  3. 返回一个函数
  4. 可以传入参数
  5. 柯里化
  6. 一个绑定的函数也能使用new操作法创建对象, 且提供的this会被忽略
Function.prototype.bind2 = function(context) {
  if (typeof this !== "function") {
    throw new Error(
      "Function.prototype.bind - what is trying to be bound is not callable"
    );
  }
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1);

  var fBound = function() {
    var innerArgs = Array.prototype.slice.call(arguments);
    return self.apply(
      this instanceof fNOP ? this : context,
      args.concat(innerArgs)
    );
  };

  var fNOP = function() {};
  fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();
  return fBound;
};
Function.prototype.bind2 = function(context) {
  // 1. 判断调用bind的是不是一个函数
  if (typeof this !== "function") {
    throw new Error(
      "Function.prototype.bind - what is trying to be bound is not callable"
    );
  }
  // 2. 外层的this指向调用者(也就是调用的函数)
  var self = this;
  // 3. 收集调用bind时的其它参数
  var args = Array.prototype.slice.call(arguments, 1);

  // 4. 创建一个返回的函数
  var fBound = function() {
    // 6. 收集调用新的函数时传入的其它参数
    var innerArgs = Array.prototype.slice.call(arguments);
    // 7. 使用apply改变调用函数时this的指向
    // 作为构造函数调用时this表示的是新产生的对象, 不作为构造函数用的时候传递context
    return self.apply(
      this instanceof fNOP ? this : context,
      args.concat(innerArgs)
    );
  };
  // 5. 创建一个空的函数, 且将原型指向调用者的原型(为了能用调用者原型中的属性)
  // 下面三步的作用有点类似于 fBoun.prototype = this.prototype 但有区别
  var fNOP = function() {};
  fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();
  // 8. 返回最后的结果
  return fBound;
};

Promise

event loop它的执行顺序:

  1. 一开始整个脚本作为一个宏任务执行
  2. 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
  3. 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
  4. 执行浏览器UI线程的渲染工作
  5. 检查是否有Web Worker任务,有则执行
  6. 执行完本轮的宏任务,回到2,依次循环,直到宏任务和微任务队列都为空

微任务包括:MutationObserver,Promise.then() 或 catch(),Promise为基础开发的其它技术,比如 fetch API , V8的垃圾回收过程,Node独有的process.nextTick。

宏任务包括: script,setTimeout,setInterval,setImmediate,I/O,UI rendering

1

// 从上至下,先遇到new Promise,执行该构造函数中的代码promise1
const promise1 = new Promise((resolve, reject) => {
 console.log('promise1') 
})

// 然后执行同步代码1,此时promise1没有被resolve或者reject,因此状态还是pending
console.log('1', promise1);

'promise1'
'1' Promise{<pending>}

2

const promise = new Promise((resolve, reject) => {
  console.log(1);
  resolve('success')
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);

// 从上至下,先遇到new Promise,执行其中的同步代码1
// 再遇到resolve('success'), 将promise的状态改为了resolved并且将值保存下来
// 继续执行同步代码2
// 跳出promise,往下执行,碰到promise.then这个微任务,将其加入微任务队列
// 执行同步代码4
// 本轮宏任务全部执行完毕,检查微任务队列,发现promise.then这个微任务且状态为resolved,执行它。

1 2 4 3

3

const promise = new Promise((resolve, reject) => {
  console.log(1);
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);

1 2 4

// 在promise中并没有resolve或者reject
// 因此promise.then并不会执行,它只有在被改变了状态之后才会执行。

4

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {
  console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);

'promise1'
'1' Promise{<resolved>: 'resolve1'}
'2' Promise{<pending>}
'resolve1'
  1. 从上至下,先遇到new Promise,执行该构造函数中的代码promise1
  2. 碰到resolve函数, 将promise1的状态改变为resolved, 并将结果保存下来
  3. 碰到promise1.then这个微任务,将它放入微任务队列
  4. promise2是一个新的状态为pending的Promise
  5. 执行同步代码1, 同时打印出promise1的状态是resolved
  6. 执行同步代码2,同时打印出promise2的状态是pending
  7. 宏任务执行完毕,查找微任务队列,发现promise1.then这个微任务且状态为resolved,执行它。

5

const fn = () => (new Promise((resolve, reject) => {
  console.log(1);
  resolve('success')
}))
fn().then(res => {
  console.log(res)
})
console.log('start')

1
'start'
'success'

fn函数它是直接返回了一个new Promise的,而且fn函数的调用是在start之前,所以它里面的内容应该会先执行。

6

const fn = () =>
  new Promise((resolve, reject) => {
    console.log(1);
    resolve("success");
  });
console.log("start");
fn().then(res => {
  console.log(res);
});

"start"
1
"success"

之前我们很容易就以为看到new Promise()就执行它的第一个参数函数了,其实这是不对的

我们得注意它是不是被包裹在函数当中,如果是的话,只有在函数调用的时候才会执行。

7

console.log('start')
setTimeout(() => {
  console.log('time')
})
Promise.resolve().then(() => {
  console.log('resolve')
})
console.log('end')

'start'
'end'
'resolve'
'time'

刚开始整个脚本作为一个宏任务来执行,对于同步代码直接压入执行栈进行执行,因此先打印出start和end。
setTimout作为一个宏任务被放入宏任务队列(下一个)
Promise.then作为一个微任务被放入微任务队列
本次宏任务执行完,检查微任务,发现Promise.then,执行它
接下来进入下一个宏任务,发现setTimeout,执行。

8

const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log("timerStart");
    resolve("success");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {
  console.log(res);
});
console.log(4);

1
2
4
"timerStart"
"timerEnd"
"success"

从上至下,先遇到new Promise,执行该构造函数中的代码1
然后碰到了定时器,将这个定时器中的函数放到下一个宏任务的延迟队列中等待执行
执行同步代码2
跳出promise函数,遇到promise.then,但其状态还是为pending,这里理解为先不执行
执行同步代码4
一轮循环过后,进入第二次宏任务,发现延迟队列中有setTimeout定时器,执行它
首先执行timerStart,然后遇到了resolve,将promise的状态改为resolved且保存结果并将之前的promise.then推入微任务队列
继续执行同步代码timerEnd
宏任务全部执行完毕,查找微任务队列,发现promise.then这个微任务,执行它。

9

setTimeout(() => {
  console.log('timer1');
  setTimeout(() => {
    console.log('timer3')
  }, 0)
}, 0)
setTimeout(() => {
  console.log('timer2')
}, 0)
console.log('start')

start
timer1
timer2
timer3
setTimeout(() => {
  console.log('timer1');
  Promise.resolve().then(() => {
    console.log('promise')
  })
}, 0)
setTimeout(() => {
  console.log('timer2')
}, 0)
console.log('start')

'start'
'timer1'
'promise'
'timer2'

Promise.then是微任务,它会被加入到本轮中的微任务列表,而定时器timer3是宏任务,它会被加入到下一轮的宏任务中。

10

Promise.resolve().then(() => {
  console.log('promise1');
  const timer2 = setTimeout(() => {
    console.log('timer2')
  }, 0)
});
const timer1 = setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('start');

'start'
'promise1'
'timer1'
'promise2'
'timer2'

刚开始整个脚本作为第一次宏任务来执行,我们将它标记为宏1,从上至下执行
遇到Promise.resolve().then这个微任务,将then中的内容加入第一次的微任务队列标记为微1
遇到定时器timer1,将它加入下一次宏任务的延迟列表,标记为宏2,等待执行(先不管里面是什么内容)
执行宏1中的同步代码start
第一次宏任务(宏1)执行完毕,检查第一次的微任务队列(微1),发现有一个promise.then这个微任务需要执行
执行打印出微1中同步代码promise1,然后发现定时器timer2,将它加入宏2的后面,标记为宏3
第一次微任务队列(微1)执行完毕,执行第二次宏任务(宏2),首先执行同步代码timer1
然后遇到了promise2这个微任务,将它加入此次循环的微任务队列,标记为微2
宏2中没有同步代码可执行了,查找本次循环的微任务队列(微2),发现了promise2,执行它
第二轮执行完毕,执行宏3,打印出timer2

11

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)

从上至下,先执行第一个new Promise中的函数,碰到setTimeout将它加入下一个宏任务列表
跳出new Promise,碰到promise1.then这个微任务,但其状态还是为pending,这里理解为先不执行
promise2是一个新的状态为pending的Promise
执行同步代码console.log('promise1'),且打印出的promise1的状态为pending
执行同步代码console.log('promise2'),且打印出的promise2的状态为pending
碰到第二个定时器,将其放入下一个宏任务列表
第一轮宏任务执行结束,并且没有微任务需要执行,因此执行第二轮宏任务
先执行第一个定时器里的内容,将promise1的状态改为resolved且保存结果并将之前的promise1.then推入微任务队列
该定时器中没有其它的同步代码可执行,因此执行本轮的微任务队列,也就是promise1.then,它抛出了一个错误,且将promise2的状态设置为了rejected
第一个定时器执行完毕,开始执行第二个定时器中的内容
打印出'promise1',且此时promise1的状态为resolved
打印出'promise2',且此时promise2的状态为rejected

1653293346(1)

12

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
    console.log("timer1");
  }, 1000);
  console.log("promise1里的内容");
});
const promise2 = promise1.then(() => {
  throw new Error("error!!!");
});
console.log("promise1", promise1);
console.log("promise2", promise2);
setTimeout(() => {
  console.log("timer2");
  console.log("promise1", promise1);
  console.log("promise2", promise2);
}, 2000);

1653293497(1)

总结

  1. Promise的状态一经改变就不能再改变。
  2. .then.catch 都会返回一个新的 Promise
  3. catch不管被连接到哪里,都能捕获上层未捕捉过的错误。
  4. 在Promise中,返回任意一个非 promise 的值都会被包裹成 promise 对象,例如return 2会被包装为return Promise.resolve(2)。
  5. Promise 的 .then 或者 .catch 可以被调用多次, 但如果Promise内部的状态一经改变,并且有了一个值,那么后续每次调用.then或者.catch的时候都会直接拿到该值。
  6. .then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获。
  7. .then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。
  8. .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。
  9. .then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为catch是.then第二个参数的简便写法。
  10. .finally方法也是返回一个Promise,他在Promise结束的时候,无论结果为resolved还是rejected,都会执行里面的回调函数。

13

构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何作用 。

const promise = new Promise((resolve, reject) => {
  resolve("success1");
  reject("error");
  resolve("success2");
});
promise
.then(res => {
    console.log("then: ", res);
  }).catch(err => {
    console.log("catch: ", err);
  })

"then: success1"

14

1653295550(1)

catch不管被连接到哪里,都能捕获上层未捕捉过的错误。

至于then3也会被执行,那是因为catch()也会返回一个Promise,且由于这个Promise没有返回值,所以打印出来的是undefined。

15

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

1
2

Promise可以链式调用,不过promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用, 它并不像一般我们任务的链式调用一样return this。
上面的输出结果之所以依次打印出1和2,那是因为resolve(1)之后走的是第一个then方法,并没有走catch里,所以第二个then中的res得到的实际上是第一个then的返回值。
且return 2会被包装成resolve(2)

16

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

1
3

因为reject(1)此时走的就是catch,且第二个then中的res得到的就是catch中的返回值。

17

1653296785(1)

18

1653296865(1)

返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的return new Error('error!!!')也被包裹成了return Promise.resolve(new Error('error!!!'))。

19

1653296946(1)

.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。

20

1653297140(1)

.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。

第一个then和第二个then中传入的都不是函数,一个是数字类型,一个是对象类型,因此发生了透传,将resolve(1) 的值直接传到最后一个then里。

21

Promise.resolve('1')的值会进入成功的函数,Promise.reject('2')的值会进入失败的函数

1653297399(1)

如果把第二个参数去掉,就进入了catch()中

1653297450(1)

22

1653297619(1)

由于Promise调用的是resolve(),因此.then()执行的应该是success()函数,可是success()函数抛出的是一个错误,它会被后面的catch()给捕获到,而不是被fail1函数捕获。

23

  1. .finally()方法不管Promise对象最后的状态如何都会执行
  2. .finally()方法的回调函数不接受任何的参数,也就是说你在.finally()函数中是没法知道Promise最终的状态是resolved还是rejected的
  3. 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。

1653298094(1)

24

1653298291(1)

25

1653298486

  1. 首先定义了两个函数promise1和promise2,先不管接着往下看。
  2. promise1函数先被调用了,然后执行里面new Promise的同步代码打印出promise1
  3. 之后遇到了resolve(1),将p的状态改为了resolved并将结果保存下来。
  4. 此时promise1内的函数内容已经执行完了,跳出该函数
  5. 碰到了promise1().then(),由于promise1的状态已经发生了改变且为resolved因此将promise1().then()这条微任务加入本轮的微任务列表(这是第一个微任务)
  6. 这时候要注意了,代码并不会接着往链式调用的下面走,也就是不会先将.finally加入微任务列表,那是因为.then本身就是一个微任务,它链式后面的内容必须得等当前这个微任务执行完才会执行,因此这里我们先不管.finally()
  7. 再往下走碰到了promise2()函数,其中返回的new Promise中并没有同步代码需要执行,所以执行reject('error')的时候将promise2函数中的Promise的状态变为了rejected
  8. 跳出promise2函数,遇到了promise2().catch(),将其加入当前的微任务队列(这是第二个微任务),且链式调用后面的内容得等该任务执行完后才执行,和.then()一样。
  9. OK, 本轮的宏任务全部执行完了,来看看微任务列表,存在promise1().then(),执行它,打印出1,然后遇到了.finally()这个微任务将它加入微任务列表(这是第三个微任务)等待执行
  10. 再执行promise2().catch()打印出error,执行完后将finally2加入微任务加入微任务列表(这是第四个微任务)
  11. OK, 本轮又全部执行完了,但是微任务列表还有两个新的微任务没有执行完,因此依次执行finally1和finally2。

26

.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。

.race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。

27

在间隔一秒后,控制台会同时打印出1, 2, 3,还有一个数组[1, 2, 3]。

1653299401(1)

  1. Promise.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
  2. .race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
  3. Promise.all().then()结果中数组的顺序和Promise.all()接收到的数组顺序一致。
  4. all和race传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中其它的异步任务的执行。

4.实现一个sum函数

  1. sum函数可以传递一个或者多个参数
  2. sum函数调用后返回的是一个新的函数且参数可传递一个或者多个
  3. 调用.valueOf时完成最后计算
sum(1, 2, 3).valueOf() // 6
sum(2, 3)(2).valueOf() // 7
sum(1)(2)(3)(4).valueOf() // 10
sum(2)(4, 1)(2).valueOf() // 9
const sum = () => {
 let args = [...arguments];
 const func = () => {
  args = [...args, ...arguments];
  return func;
 }
 func.valueOf = () => args.reduce((cur, prev) => cur + prev);
 return func;
}
const sum = (...args1) => {
  const fullArgs = [...args1];
  const fn = (...args2) => {
    // 收集参数
    fullArgs.push(...args2)
    // 返回自身保持链式调用
    return fn;
  };

  // 重写valueOf,累加已收集的参数
  fn.valueOf = () => fullArgs.reduce((total, cur) => total + cur)

  return fn;
};

7.实现instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);

console.log(auto instanceof Car);
//  true

console.log(auto instanceof Object);
//  true

A构造函数 -> 实例化 -> 实例a

实例a -> __proto__ -> A.prototype -> __proto__ -> Object.prototype -> __proto__ -> null

Function构造函数 -> 原型 -> Function.prototype -> __proto__ -> Object.prototype -> __proto__ -> null

A构造函数 -> __proto__ -> Function.prototype -> __proto__ -> Object.prototype -> __proto__ -> null

Object构造函数 -> 原型 -> Object.prototype -> __proto__ -> null

Object构造函数 -> __proto -> Function.prototype -> __proto__ -> Object.prototype->proto` -> null

Function构造函数 -> 实例化 -> A构造函数 -> 实例化 -> 实例a

Function构造函数 -> 实例化 -> Object构造函数 -> __proto__ -> Function.prototype -> __proto__ -> Object.prototype -> __proto__ -> null

语法

object instanceof constructor : object 某个实例对象 constructor 某个构造函数

instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上

// 定义构造函数
function C(){}
function D(){}

var o = new C();


o instanceof C; // true,因为 Object.getPrototypeOf(o) === C.prototype


o instanceof D; // false,因为 D.prototype 不在 o 的原型链上

o instanceof Object; // true,因为 Object.prototype.isPrototypeOf(o) 返回 true
C.prototype instanceof Object // true,同上

C.prototype = {};
var o2 = new C();

o2 instanceof C; // true

o instanceof C; // false,C.prototype 指向了一个空对象,这个空对象不在 o 的原型链上.

D.prototype = new C(); // 继承
var o3 = new D();
o3 instanceof D; // true
o3 instanceof C; // true 因为 C.prototype 现在在 o3 的原型链上
var simpleStr = "This is a simple string";
var myString  = new String();
var newStr    = new String("String created with constructor");
var myDate    = new Date();
var myObj     = {};
var myNonObj  = Object.create(null);

simpleStr instanceof String; // 返回 false, 非对象实例,因此返回 false
myString  instanceof String; // 返回 true
newStr    instanceof String; // 返回 true
myString  instanceof Object; // 返回 true

myObj instanceof Object;    // 返回 true, 尽管原型没有定义
({})  instanceof Object;    // 返回 true, 同上
myNonObj instanceof Object; // 返回 false, 一种创建非 Object 实例的对象的方法

myString instanceof Date; //返回 false

myDate instanceof Date;     // 返回 true
myDate instanceof Object;   // 返回 true
myDate instanceof String;   // 返回 false

要检测对象不是某个构造函数的实例时,你可以这样做

if (!(mycar instanceof Car)) {
  // Do something, like mycar = new Car(mycar)
}
function instanceof (L, R) {
 // L实例对象
 // R构造函数
 // 取R的显式原型
 let O = R.prototype;
 // 取L的隐式原型
 L = L.__proto__;
 while(true) {
  // 循环遍历
  if(L===Null) {
   return false;
  }
  if(O === L) {
   return true;
  }
  L = L.__proto__;
 }
}
function instanceof(left,right){
    // 获取类型的原型
   let prototype = right.prototype
   // 获取对象的原型
   left = left._proto_
   //判断对象的类型是否等于类型的原型
   while(true){
       if(left === null)
          return false
       if(prototype === left)
          return true
      left = left._proto_
   }
}
function myInstanceof(left, right) {
  let proto = Object.getPrototypeOf(left) // 获取对象的原型
  let prototype = right.prototype;        // 获取构造函数的 prototype 对象

  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (true) {
    if (!proto) return false;
    if (proto === prototype) return true;

    proto = Object.getPrototypeOf(proto)
  }
}
function InstanceOf({ __proto__ }, { prototype }) {

  while (true) {

    if (!__proto__) return false

    if (__proto__ === prototype) return true

    __proto__ = __proto__.__proto__
  }
}
function instanceofMock(L, R) {
  // L 必须是实例对象
  // R 必须是构造函数
  if (L === null || typeof R !== 'function') {
    return false;
  }

  while (true) {
    // 已经遍历到最顶端
    if (L === null) {
      return false;
    }

    if (R.prototype === L.__proto__) {
      return true;
    }

    // 一直顺着 __proto__ 找
    L = L.__proto__;
  }
}

第一次判断是说,如果传递进来是 null 或者是 第二个参数压根就不是一个function,这种情况,永远都是false了。

while 里面的 ,因为我对L进行了迭代取值,不停的取 protocol,所以这时候 L最终可能会达到 null。

2.手写new和bind

new 运算符:

运算符创建一个用户定义的对象类型的实例 或 具有构造函数的内置对象类型之一。

bind 函数:

方法会创建一个新函数,当这个新函数被调用时,bind()的第一个参数将作为它运行时的this,之后的一系列参数将会传递的实参前传入作为它的参数。

bind函数的特点:

  1. 改变this指向
  2. 第一个参数是this的值,后面的参数是函数接收的参数的值
  3. 返回值不变
// 手写bind
Function.prototype.myBind = function() {
 // 为了获取原函数
 const self = this;

 // 执行slice方法,其中的this指向arguments,得到真正的数组
 const args = Array.prototype.slice.call(arguments);

 // 拿到第一个参数
 const thisValue = args.shift();

 return function() {
  // 返回一个函数,它是一个匿名函数,整个匿名函数,就是保存在变量中的新的函数
  // 返回新函数里执行原函数
  return self.apply(thisValue, args);
 }
}

new原理:

  1. 创建空对象,作为将要返回的对象实例
  2. 指向原型,将这个空对象的原型,指向构造函数的prototype属性
  3. 绑定this,将这个空对象赋值给函数内部的this关键字
  4. 返回新对象,开始执行构造函数内部的代码
// 手写new
function myNew(fn, ...args) {
 // 定义个空对象
 const obj = {};

 // 隐式原型指向构造函数的显式原型
 obj.__proto__ = fn.prototype;

 // 执行构造函数,this指向空对象
 fn.apply(obj, args);

 // 返回对象
 return obj;
}
// new,创建实例,构造函数,剩余参数
const createInstance = (Constructor, ...args) => {
 // 创建对象,并指向构造函数的原型
 const obj = Object.create(Constructor.prototype);

 // 构造函数内部的 this 指向 instance 变量
 let res = Constructor.call(obj, ...args);

// 判断 是否是 函数或对象
const isObj = res !== null && typeof res === 'object';
const isFunc = typeof res === 'function';

 const isFunc || isObj ? res : obj;
}
// objectFactory
function objectFactory() {
 var object = new Object();
 var Constructor = Array.prototype.shift.call(arguments);
 // 原型式继承
 object.__proto__ = Constructor.prototype;
 // arguments第一个已经被shift,此时arguments是我们想要的参数
 var result = Constructor.apply(object, arguments);
 return typeof result === 'object'  ? result : object;
}

new的运行过程:

  1. 新建一个内部对象
  2. 给这个对象指定一个隐式原型链,对象的__proto__指向构造函数的prototype
  3. 指定对象的this
  4. 执行构造函数内部的代码
  5. 如果有返回值,就返回构造函数的返回值,否则就返回新建的对象
function objectFactory() {
 const constructor = [].shift.call(arguments)

 // 新建一个对象,并且将对象的隐式原型指定为构造函数的显示原型
 // 这样就能访问到构造函数的原型中的属性
 let newObject = Object.create(constructor.prototype);
 
 // apply将 this 指定为 newObject
 let res = constructor.apply(newObject, arguments);
 
 // 判断 构造函数 返回的值是否是对象
 return typeof res === 'object' ? res : newObject
}

模拟手写new

// new
function create(Con, ...args) {
 let obj = {}
 // Object.getPrototypeOf(obj) 相当于obj.__proto__
 Object.setProrotypeOf(obj, Con.prototype);
 let result = Con.apply(obj, args)
 return result instanceof Object ? result : obj
}

模拟手写bind

Function.prototype.myBind = function(context) {
 if(typeof this !== 'function') {
  throw new TypeError('Error');
 }
 var _this = this
 var args = [...arguments].slice(1)
 return function F() {
  if (this instanceof F) {
   // instanceof 返回true
  return new _this(...args, ...arguments);
  }
  return _this.apply(content, [...args, ...arguments])
 }
}

new关键字实现了什么

  1. this指向新创建的对象
  2. 对象的[[prototype]]属性指向构造函数的prototype
  3. 执行函数,返回一个新对象
  4. 如果函数没有返回对象类型Object,那么new表达式中的函数调用会自动返回这个新的对象
function objectFactory(func) {
 if(typeof func !== 'functio') {
   throw new Error('请传入一个函数')
 }

 let obj = Object.create(func.prototype);
 let args = [...arguments].slice(1);
 const result = func.apply(obj, args);
 
 let objectResult = typeof result === 'object' && result !== null;
 let funcResult = typeof result === 'function';

 return (objectResult || funcResult) ? result : obj;
}

bind实现了什么

  1. bind返回一个函数
  2. bind可以传入参数
  3. bindFunc返回的函数也可以传入参数
  4. this指向bindObjNew
Function.prototype.myBind = function(context) {
 if(typeof this !== 'function') {
  throw new Error('请输入一个函数');
 }
 let args = [...arguments].slice(1);
 let _this = this;

 let fBound = function() {
  let args2 = [...arguments];
  // return _this.apply(context, [...args, ...args2]);
 return _this.apply(this instanceof fBound ? this : context, [..args, ...args2])
 }
 return fBound; 
}

Bind函数实现:

  1. 返回一个函数
  2. 可以传入参数
Function.prototype.myBind = function(context) {
 if(typeof this != 'function') {
  throw new TypeError('error')
 }
 
 let _this = this
 // 函数剩余的参数
 let args = [...arguments].slice(1)

 return function F() {
  // 因为返回的是一个函数,所以可以 new F(),所以要进行判断
  if(this instanceof F) {
   return new _this(...args, ...arguments);
  }
  return _this.apply(context, args.concat(...arguments));
 }
}

New 实现:

  1. 创建了一个新对象
  2. 链接到原型
  3. 绑定到this
  4. 返回新对象
function objectFactory() {
 // 新创建一个对象
 let obj = {}
 
 // 取出构造函数
 constructor = [].shift.call(arguments);
 // 链接到原型
 obj.__proto__ = constructor.prototype;
 // 绑定到this
 constructor.apply(obj, arguments);
 // 返回新对象
 return obj;
}

new 关键字做了什么?

  1. 一个新的对象
  2. 新对象的__proto__指向构造函数的prototype
  3. 使用apply改变构造函数的this指向,使其指向新对象,这样一来obj就有构造函数里面的属性啦
  4. 判断构造函数是否有返回值,如果构造函数返回对象,则直接返回构造函数中的对象
  5. 返回新对象
function myNew() {
  let obj = new Object();
  let constrctor = Array.prototype.shift.call(arguments);
  obj.__proto__ = constrctor.prototype;
  let res = constrctor.apply(obj, arguments);
  return typeof res === "object" ? res : obj;
}

bind:

Function.prototype.Mybind = function (context) {
  if (typeof this !== "function") return "我们要函数才可以调用哦~";
  let myFn = this;

  // 当前调用传进来的参数
  let arg = Array.prototype.slice.call(arguments, 1);

  let fNOP = function () {};

  let fBound = function () {
    let bindArg = Array.prototype.slice.call(arguments);
    // 当函数作为构造函数时,this会指向他的实例fbound
    // 当函数作为普通函数的时候,this会指向window
    return myFn.apply(
      this instanceof fNOP ? this : context,
      bindArg.concat(arg)
    );
  };
  // 修改函数的原型指向this的原型 实例可以继承绑定函数的原型中的值,因为是引用类型~
  fBound.prototype = this.prototype;
  // 中转一下就不会修改到绑定函数的this啦
  fBound.prototype = new fNOP();
  return fBound;
};

new实践

Object.create = function(o) {
 function f() {}
 f.prototype = o
 return new f;
}
function objectFactory() {
 var obj = new Object(), // 从Object.prototype上克隆一个对象
 Constructor = [].shift.call(arguments); // 取得外部传入的构造器
 
 var F = function() {}
 F.prototype = Constructor.prototype;
 obj = new F(); // 指向正确的原型

 var res = Constructor.apply(obj, arguments); // 借用外部传入的构造器给obj设置属性
 return typeof res === 'object' ? res : obj; // 确保构造器总是返回一个对象
}
Function.prototype.myBind = function(context) {
 if (typeof this !== 'function') {
  throw new Error('Function.prototype.bind - what is trying to be bound is not callable');
 }
 
 var selft = this;
 var args = Array.prototype.slice.call(arguments, 1);
 
 var fNOP = function() {};
 
 var fBound = function() {
  var bindArgs = Array.prototype.slice.call(arguments);
  return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
 }
 fNOP.prototype = this.prototype;
 fBound.prototype = new fNOP();
 return fBound;
}

10.实现原型链继承、借用构造函数继承、组合继承

继承

  1. 原型链
  2. 盗用构造函数
  3. 组合继承
  4. 原型式继承
  5. 寄生式继承
  6. 寄生组合继承

基本**就是通过 原型 继承 多个引用类型的属性和方法

每个构造函数都有一个原型对象,原型有一个属性指向构造函数,而实例有一个内部指针指向原型。

原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本构想。

1. 默认原型

默认情况下,所有引用类型都继承自 Object,这也是通过原型链实现的。任何函数的默认原型都是一个 Object 的实例,这意味着这个实例有一个内部指针指向Object.prototype。

2. 原型与继承关系

原型与实例的关系可以通过两种方式来确定

第一种方式是使用 instanceof 操作符

第二种方式是使用 isPrototypeOf()方法 原型链中的每个原型都可以调用这个方法

// 有效
// 新方法
SubType.prototype.getSubValue = function () {
 return this.subproperty;
};
// 覆盖已有的方法
SubType.prototype.getSuperValue = function () {
 return false;
};

// 无效 以对象字面量方式创建原型方法会破坏之前的原型链
SubType.prototype = {
 getSubValue() {
 return this.subproperty;
 },
 someOtherMethod() {
 return false;
 }
}; 

原型链的问题

主要问题出现在原型中包含引用值的时候。原型中包含的引用值会在所有实例间共享,这也是为什么属性通常会在构造函数中定义而不会定义在原型上的原因。

在使用原型实现继承时,原型实际上变成了另一个类型的实例。

原型链的第二个问题是,子类型在实例化时不能给父类型的构造函数传参。

如何用原型链的方式实现一个 JS 继承?

当我们对使用 new 关键字创建对象,被创建的对象的 [[prototype]] 会指向这个 prototype。

function Rect() {}
const rect = new Rect()
rect.__proto__ === Rect.prototype // true
Rect.prototype.constructor === Rect // true

image

用原型链的方式实现继承

// 父类
function Shape() {}
Shape.prototype.draw = function() {
  console.log('Shape Draw')
}
Shape.prototype.clear = function() {
  console.log('Shape Clear')
}
// 子类
function Rect() {}
/** 
 实现继承的代码放这里
**/
Rect.prototype.draw = function() {
  console.log('Rect Draw')
}

方法1:Object.create

Rect.prototype = Object.create(Shape.prototype)
Rect.prototype.constructor = Rect // 选用,如果要用到 constructor

Object.create(proto) 是个神奇的方法,它能够创建一个空对象,并设置它的 [[prototype]] 为传入的对象。

因为我们无法通过代码的方式给 [[prototype]] 属性赋值,所以使用了 Object.create 方法作为替代。

因为 Rect.prototype 指向了另一个新的对象,所以把 constructor 给丢失了,可以考虑把它放回来,如果你要用到的话。

缺点是替换掉了原来的对象。

image

方法2:直接修改 [[prototype]]

但不推荐。

Object.setPrototypeOf() 可以修改对象的 [[prototype]],但因为性能的问题,也不推荐使用。

方法3:使用父类的实例

Rect.prototype = new Shape()
rect -> shape(替代掉原来的 Rect.prototype) -> Shape.prototype -> Object.prototype -> null

缺点是会产生副作用,就是执行 new Shap() 可能会出现副作用

借用构造函数

借用构造函数(Constructor Stealing),即在子类型构造函数的内部调用父类构造函数以实现对父类构造函数属性的继承。

function SubType() {
 // 继承 SuperType
 SuperType.call(this);
}

传递参数

相对于原型链而言,借用构造函数有一个很大的优势,即 可以在子类型构造函数中向父类型构造函数传递参数。

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

function Child() {
  //继承了 Parent,同时还传递了参数
  Parent.call(this, 'dada');

  //实例属性
  this.age = 18;
}

const child = new Child();
console.log(child.name);
// 'dada'
console.log(child.age);
// 18
  1. 通过往父类型构造函数传递参数,能自定义需要继承的属性
  2. 为了确保子构造函数自身定义的属性或方法不被父构造函数生成的属性重写,可以在调用父类型构造函数后,再添加子类型构造函数中定义的属性

缺陷

  1. 只能继承父类实例对象的属性和方法,不能继承原型对象的属性和方法
  2. 无法实现复用,每个子类都有父类实例函数的副本,影响性能

JavaScript如何借用构造函数继承

function girlFriend(){
    this.girls = ['chen','wang','zhu'];
  }
  function Person(){
    girlFriend.call(this,20);
  }
  var wang = new Person();
  var zhu = new Person();
  wang.girls.push('zhang');
  console.log(wang.girls);  //(4) ["chen", "wang", "zhu", "zhang"]
  console.log(zhu.girls);    //(3) ["chen", "wang", "zhu"]

在原型链继承中出现的问题不再出现了,这个超类不会被子类所创建的实例共享了。

借用构造函数继承的优势:是可以在子类型构造函数中向超类型构造函数传递参数

借用构造函数继承的问题:用构造函数继承并不能继承到超类型原型中定义的方法

组合继承

组合继承(Combination Inheritance)(也叫伪经典继承),指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。

其背后的思路是使用原型链实现对原型对象的属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

function SubType(name, age){
 // 继承属性
 SuperType.call(this, name);
 this.age = age;
}
// 继承方法
SubType.prototype = new SuperType();

组合继承弥补了原型链和盗用构造函数的不足,是 JavaScript 中使用最多的继承模式。

组合继承也保留了 instanceof 操作符和 isPrototypeOf()方法识别合成对象的能力。

缺陷

无论什么情况下,都会调用两次父类构造函数:第一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

组合继承优化

Child.prototype = new Parent();


Child.prototype = Parent.prototype;

寄生组合式继承

function Child() {
  Parent.call(this);
  thi.type = 'Child';
}

Child.prototype = Object.create(Parent.prototype);

Child.prototype.constructor = Child;

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.