面向方针编程思想

多态——多种形态(一个人多面性,在运用时向外暴露类型,趋于抽象。);也就是常说的“鸭子模型”

JavaScript 的“鸭子类型”是指一种动态类型言语的编程风格,一般用于形容在运行时,只需一个方针具有某些办法或特点,就能够以为它是某种类型的实例,即不要求严格的类型一致性,只需形状类似就能够以为是同一类型。

比方,一个“看起来像鸭子、走路像鸭子、叫声像鸭子”,那么咱们就能够以为这个方针就是一个鸭子。

在 JavaScript 中,因为言语的动态性,方针的类型是在运行时决定的,而不是在编译时静态确定的。因而,咱们能够用同样的办法对待不同形状的方针。(Taro内部完成也有编译时+运行时

了解 JavaScript 的鸭子模型能够协助咱们更好地了解 JavaScript 的灵敏性。咱们无需强制限制一个方针有必要是某个特定类的实例,而是能够依据其形状和行为来安排代码。这在许多场合下十分有用,比方在写函数时承受恣意类型的参数,或许完成面向方针的承继时允许多态性。

举个比方,咱们能够写一个函数来查看一个方针是否具有某个办法:

function hasPrint(obj) {
//做这个判别时,首要需求确定入参是否为一个方针
/*
typeof obj === "object";
这个判别一定能判别出传人的obj就是一个方针吗?
不能的,因为还有null,null也是方针,可是null没有print办法
return typeof obj === "object" && obj.print;
obj.print一定能确保print办法存在吗?
*/
  return obj && typeof obj.print === 'function';
}

运用 prototype.toString 能够去代替 typeof 的检测,但不行完全代替, typeof 有更大包容性

然后咱们能够传入任何方针,只需它具有 print 办法,就能够经过函数的查看。

const obj1 = { print: () => console.log('Object 1') };
const obj2 = { someOtherMethod: () => console.log('Object 2') };
const obj3 = { print: () => console.log('Object 3') };
console.log(hasPrint(obj1)); // true
console.log(hasPrint(obj2)); // false
console.log(hasPrint(obj3)); // true

虽然这些方针的类型不同,可是只需它们包括了 print 办法,咱们就能够以为它们同属于某个特定类型(比方说“能够打印”的类型)。这就是 JavaScript 中鸭子模型的表现。

规划的实质就是向上抽象

class Person{
    say(){
        //一般在规划时,会屏蔽掉一些不必要的细节,只关心入参及回来值
        console.log("Person");
    }
    work(){
        console.log("work");
    }
}
const p=new Person();
console.log(p);

咱们能够类比 Java
Java 中:类 → 抽象类 → 接口

面向方针编程的重要特性

  • 封装
  • 承继
  • 多态性

在 JavaScript 中,封装、承继和多态是面向方针编程的中心概念。下面分别介绍这三个概念。

封装(Encapsulation)

封装是指将方针的状况(即特点)和行为(即办法)包装在一起,对外隐藏方针的部分特点和办法,只保留对外揭露的接口。这样能够维护方针内部的状况不受外界干扰,然后更好地控制方针的拜访和修正。

在以前,咱们一般会运用闭包等办法去完成这样一个封装的处理

在 JavaScript 中,常见的封装办法是运用闭包来封装方针的私有特点和办法。例如:

function Counter() {
  let count = 0;
  this.increment = function() {
    count++;
  };
  this.get = function() {
    return count;
  };
}
let counter = new Counter();
console.log(counter.get()); // 输出 0
counter.increment();
console.log(counter.get()); // 输出 1

上面的代码中,Counter 函数运用闭包封装了 count 变量,使其只能经过 increment 和 get 办法进行拜访和修正,而不能直接拜访或修正。这样能够有效地维护 count 变量的安全性。

承继(Inheritance)

承继是指子类承继父类的特点和办法,并能够在此基础上增加新的特点和办法。承继使得代码的复用和扩展变得更加容易。

当咱们规划程序的时候,假如是面向方针的思路,首要需求想到的就是规划基类
eg:
规划个计算器,计算面板+显示屏+外围结构
规划个编辑器,光标cursor、选区 selection、redo/undo manager

//基类
//称之为结构器函数
Function Fruit(name){
    this.name=name;
}
const apple=new Fruit("apple");
const peach=new Fruit("peach");
console.log(apple,peach);

在 JavaScript 中,能够运用原型链完成承继
所以先浅浅回顾一下原型和原型链(在后面再具体补充):

面向对象编程思想与原型继承
js分为函数方针普通方针每个方针都有__proto__特点,可是只需函数方针且非箭头函数才有prototype特点

function Person() {}
Person.prototype.name = "mick"
var person1 = new Person()
var person2 = new Person()
console.log(person1.name) // mick
console.log(person2.name) // mick
//每一个JS方针(除了null)都具有一个特点叫 __proto__ ,这个特点会指向该方针的原型
console.log(person1.__proto__ === Person.prototype) // true
//每一个原型都有一个 constructor 特点指向相关的结构函数
console.log(Person === Person.prototype.constructor) // true

能够看到结构函数Person有一个特点是prototype。其实这个特点指向了一个方针,这个方针正是调用结构函数而创立的实例的原型,也就是person1person2的原型

原型链: 当咱们读取实例上的一个特点的时候,假如找不到,就会查找与实例相关的原型中的特点,假如还是找不到,就去找原型的原型,一直找到最顶层为止

function Person() {}
Person.prototype.name = "mick"
var person = new Person()
person.name = "randy"
console.log(person.name) // randy
delete person.name
console.log(person.name) // mick
//当咱们删除了name特点的时候,那么在 person的实例 上就找不到name特点了,那就会从 person实例的原型 查找,也就是person.__proto__,也就是Person.prototype查找,找到了`mick`。
//假如`Person.prototype`也没找到
//就要了解原型的原型 Object.prototype
//console.log(Object.prototype.__proto__ === null)  true

那么怎样运用原型链完成承继呢:
一个方针的原型方针能够被指定为另一个方针,这样就能够完成特点和办法的承继。假设有两个结构函数 Person 和 Student:

function Person(name) {//其实这个就是一个基类(结构函数)
  this.name = name;
}
Person.prototype.sayHello = function() {
  console.log('Hello, my name is ' + this.name);
};
function Student(name, grade) {
  Person.call(this, name);//call()是将Person的特点绑定到当时(this也就是Student)办法中,让其具有相同的特点;
  this.grade = grade;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
Student.prototype.sayGrade = function() {
  console.log('My grade is ' + this.grade);
};

上面的代码中,Student 结构函数调用了 Person 结构函数,并运用 Object.create() 办法将 Person.prototype 设置为自己的原型,完成对 Person 特点和办法的承继。然后,Student.prototype 增加了自己的新特点gradae和办法 sayGrade

怎样去创立一个没有原型的方针? Object.create(null)能够创立一个没有原型的方针

多态(Polymorphism)

多态是指相同的操作效果于不同的方针上,能够产生不同的成果。在面向方针编程中,多态运用的最重要的就是重写(办法的姓名相同,可是参数和完成不同)以及重载(办法的姓名相同,可是参数不同)。运用多态能够使得代码更加灵敏和可扩展。

在 JavaScript 中,因为言语的动态特性和“鸭子类型”的支撑,多态的完成更加自然。一个方针的办法能够被任何方针调用,只需方针具有相同的办法名和参数即可,这就完成了多态性。

function speak(animal) {
  if (animal && typeof animal.speak === 'function') {
    animal.speak();
  }
}
let cat = {
  speak: function() {
    console.log('Meow!');
  }
};
let dog = {
  speak: function() {
    console.log('Woof!');
  }
};
let cow = {};
speak(cat); // 输出 "Meow!"
speak(dog); // 输出 "Woof!"
speak(cow); // 什么也不输出

上面的代码中,speak 函数能够承受任何方针作为参数,只需这个方针具有 speak 办法,就能够调用该办法,完成了多态性。

原型承继

在早期, JavaScript 中完成承继的办法是:原型承继,在现在的新语法中咱们很少能看到原型的身影,可是这一特性咱们需求了解并把握。

基本概念

当咱们创立 JavaScript 方针时,JavaScript 解说器会主动为这个方针增加一个特殊的特点和办法,它们是 protoprototype。用起来有些容易混杂,咱们逐一进行介绍。

proto

proto 是 JavaScript 中每个方针都有的特点,它是一个指向该方针原型指针 。每个 JavaScript 方针都有一个原型,也就是该方针从其父方针承继特点和办法。在创立方针时,__proto__ 会被初始化为其结构函数的原型方针,它能够指向另一个方针,也能够是 null。
例如,以下代码创立了一个名为person的方针,它的原型指向 Object.prototype

let person = {};
console.log(person.__proto__ === Object.prototype); // true

类实例化的方针,__proto__指向这个类结构器的prototype

function Person (name) {
  this.name = name;
}
const p=new Person();
p.__proto__ === Person.prototype; 

const a = {} a 是由Object类结构器实例化来的
方针实例化的办法:字面量

const arr = []; const obj = {};//Array,Object
const arr = new Array(); const obj = new Object()

-> === 的含义是什么,跟 == 的区别是什么?
==的含义是相等,===的含义是完全相同。
==只需求值相等; ===要求值和类型都相等

prototype

prototype 是函数方针中的特点,每个函数均默许具有一个 prototype 方针(除了Function.prototype.bind() 这个特殊函数),其实例方针能够承继这个 prototype 方针上的特点和办法。

咱们能够运用结构函数和 prototype 创立自界说函数,并经过new实例化一个方针。
例如,以下代码创立一个轿车类 Car, 并在其 prototype 上界说了一个 drive 办法:

function Car() {}
Car.prototype.drive = function () {
    console.log('Driving the car!')
}
let myCar = new Car()
myCar.drive() // Driving the car!

留意,在这个比方中,实例方针 myCar 承继了经过 prototype 界说的 drive() 办法。

能够看出,__proto__是一个指向内部 prototype 特点指针的引证,而 prototype 是函数方针的一个特点,指向一个方针,它被称作这个函数的原型方针。简而言之,__proto__ 是拜访方针原型链上一层的方针,prototype 是在界说方针结构函数时设置它的原型,以处理承继问题。

原型承继的完成办法

原型承继是 JavaScript 中十分重要的概念。它能够让咱们经过承继父方针的特点和办法来创立一个新方针。JavaScript 中的一切方针都有一个原型,而原型也能够具有自己的原型,形成了一个原型链。下面介绍几种完成原型承继的办法。

原型链承继

原型链承继是最常见的一种完成办法。其基本思想是运用原型链来完成承继

function Parent() {
  this.name = "parent";
}
Parent.prototype.sayName = function() {
  console.log(this.name);
}
function Child() {
  this.name = "child";
}
//缺点:这儿创立的Parent只需一个,能够了解为一个单例
Child.prototype = new Parent();
//因为它是单例的,所以有个很大的问题:它的prototype是能够更改的,一旦它更改了,内容会悉数变更
//Child.prototype.sayName= {}
//这样sayName就不再是办法了,参数也无法成功传进去
let child = new Child();
child.sayName(); // "child"

在这个比方中,咱们界说了一个父结构函数 Parent 和一个子结构函数 Child。经过将 Child.prototype 指向一个父方针的实例,咱们完成了子方针承继父方针的特点和办法。现在,子方针 child 具有了父原型上的sayName() 办法。

由此:

Child.__proto__===Parent.prototype;//true
Child.__proto__===Object.prototype;//false

因为object在child的上两层,而由上述咱们现已了解到了:__proto__ 是拜访方针原型链上一层的方针,所以第二行回来false。

结构函数的借用

借用结构函数也是一种原型承继的办法,该办法的基本思想是经过调用父结构函数并将子结构函数作为上下文来初始化特点

function Parent(name) {
  this.name = name || "default name";
}
Parent.prototype.sayName = function() {
  console.log(this.name);
}
//结构器借用完成承继
function Child(name) {
  this.talk=function(){
      console.log("talk");
  }
  Parent.call(this, name);//这儿的this指向的是Child的实例
}
let c = new Child("child1");
c.sayName();// "child1"
c.talk();
//怎样来判别这两个办法哪个是Child自己的办法,还是经过承继来的办法?
//c.hasOwnProperty('sayName'); true
//c.hasOwnProperty('talk'); true
//hasOwnProperty用于判别某个特点是否是方针本身的特点还是原型链上面的特点
//在实例化方针c时,它们会成为a方针自己的特点和办法,而不是承继自原型链,所以上述比方均回来true

在这个比方中,咱们界说了一个父结构函数 Parent 和一个子结构函数 Child。在创立子结构函数的实例时,咱们运用了 call() 函数来调用父结构函数并将其子结构函数作为上下文来初始化子方针的特点。这样,子方针就承继了父方针的特点。

组合承继

组合承继(也称之为伪经典承继)是根据以上两种办法的一个混成办法,其基本思想是组合运用原型链承继和借用结构函数。

function Parent(name) {
  this.name = name || "default name";
}
Parent.prototype.sayName = function() {
  console.log(this.name);
}
function Child(name) {
//结构器借用
  Parent.call(this, name);
}
//原型承继
Child.prototype = new Parent();
let child1 = new Child("child1");
console.log(child1.name); // "child1"

在这个比方中,咱们界说了一个父结构函数 Parent 和一个子结构函数 Child。在创立子结构函数的实例时,咱们运用 call() 函数来调用父结构函数并将其子结构函数作为上下文来初始化子方针的特点,然后将子结构函数的原型等于一个父方针的实例。经过这种办法,子方针成功地承继了父方针的特点和办法。
以上三种办法均能够完成原型承继,但它们各自有优缺点,咱们应依据需求选用适宜的办法来完成承继。

完成原型承继的技巧

时刻留意原型链上的引证值

在 JavaScript 中,原型承继是经过同享原型特点和办法来完成的。因而,在原型链上的一个方针上更改引证类型的特点会影响一切方针。例如,以下代码演示了这个问题。

function Person(name) {
  this.name = name;
}
Person.prototype.sayName = function() {
  console.log(this.name);
}
function Student(name, grade) {
  this.grade = grade;
  Person.call(this, name);
}
Student.prototype = new Person();
let student1 = new Student("John", 3);
let student2 = new Student("Jane", 4);
student1.sayName(); // John
student2.sayName(); // Jane
Student.prototype.sayGrade = function() {
  console.log(`Grade: ${this.grade}`);
}
student1.sayGrade(); // Grade: 3
student2.sayGrade(); // Grade: 4
Student.prototype.grade = 3;
student1.sayGrade(); // Grade: 3
student2.sayGrade(); // Grade: 3

在这个比方中,咱们界说了一个父结构函数 Person 和一个子结构函数 Student。经过在子结构函数的原型特点上赋值一个父方针的实例来完成承继。然后,咱们在 Student.prototype 上界说了一个 sayGrade() 办法,并将 grade 设置为 3。当咱们运用方针 student1student2 调用 sayGrade()时,它们都输出了 3。因而,咱们需求时刻留意原型链上的引证值并防止在原型链上更改引证类型的特点。

防止重写原型

在原型承继中,运用的是方针引证,因而,在重写原型时要十分当心。假如不当心重写原型,或许会导致之前一切承继了原型链的方针上的办法和特点悉数失效,这是一个十分危险的操作。因而,在重写原型时,咱们应该将其设置为新方针的实例,而不是直接赋值。

function Person(name) {
  this.name = name;
}
Person.prototype.sayName = function() {
  console.log(this.name);
}
function Student(name, grade) {
  this.grade = grade;
  Person.call(this, name);
}
// Bad practice
 Student.prototype = { 
    sayGrade: function() { 
        console.log(`Grade: ${this.grade}`);
    }
};
// Good practice
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
Student.prototype.sayGrade = function() {
  console.log(`Grade: ${this.grade}`);
}

在上面的代码中,咱们界说了一个父结构函数 Person 和一个子结构函数 Student。然后咱们界说 Student.prototype,运用了 Object.create() 来设置新方针的原型为 Person.prototype。这样,咱们成功地承继了父方针的特点和办法,而没有重写原型。

面向方针编程进阶

提到面向方针,咱们提的更多的就是方针,咱们许多时候都是在处理方针内容,比方在 react 中,咱们的状况是方针类型,咱们需求将该数据渲染到页面中。

方针相关办法

JavaScript 中 Object 类型内置有许多有用的办法,这儿列举一些常用的办法:

  1. Object.keys(obj):回来一个包括一切可枚举特点称号数组(不包括从原型承继而来的特点)。
  2. Object.values(obj):回来一个包括一切可枚举特点值数组
  3. Object.entries(obj):回来一个包括一切可枚举特点称号和特点值二维数组
  4. Object.hasOwnProperty(prop):查看一个方针是否含有指定称号的特点(不包括从原型承继而来的特点)。
  5. object.defineProperty(object,key,options):为方针界说特点
  6. Object.freeze(obj)冻住一个方针,使其不能增加、删除、修正特点,也不能修正特点的特性
  7. Object.seal(obj)密封一个方针,使其不能增加、删除特点,但能够修正特点的值和特性
  8. Object.assign(target, s1, s2, …):将一个或多个源方针的一切可枚举特点复制到方针方针

其间,Object.keys()Object.values()Object.entries() 都是 ES6 新增的办法,在旧版本浏览器中不一定支撑。

署理方针(Proxy)

别的,JavaScript 中还有 Object 类型的一个十分有用的特性——署理方针(Proxy)。署理方针允许你阻拦对方针方针的操作,并界说自己的行为。它的语法为: new Proxy(target, handler)

其间,target 是要署理的方针方针,handler 是一个处理器方针,包括用于阻拦各种操作的办法。 下面是一个比方:

let obj = {
  name: 'John',
  age: 30
};
let proxy = new Proxy(obj, {
  get(target, prop, receiver) {
    console.log('get ' + prop);
    return target[prop];
  },
  set(target, prop, value, receiver) {
    console.log('set ' + prop + ' to ' + value);
    target[prop] = value;
    return true;
  }
});
console.log(proxy.name); // 输出 "get name" 和 "John"
proxy.age = 31; // 输出 "set age to 31"
console.log(proxy.age); // 输出 "get age" 和 "31"

上面的代码创立了一个署理方针 proxy,然后经过 get 和 set 办法阻拦了对方针方针 obj 的读写操作,并在控制台输出了相关信息。
在实际运用中,署理方针的用途十分广泛,比方能够用来完成数据绑定数据验证和缓存等功能。需求留意的是,署理方针不能阻拦一些原生的操作,比方 Object.defineProperty() 办法

署理方针(Proxy)实战

十分流行的库——immer,就是运用了 proxy 这一特性,完成了 immutable 处理,官方原话:

Immer (German for: always) is a tiny package that allows you to work with immutable state in a more convenient way
Immer是一个小巧的工具包,能够更方便地运用不行变状况。

Immer 基本概念

immer 是一款十分流行的 JavaScript ,用于办理 JavaScript 方针的不行变状况。immer 运用 ES6 的 Proxy 方针来捕获对方针的修正,并回来一个可变的署理方针。在 immer 库内部,这个署理方针被用于追寻每次对方针的修正,最终生成一个全新的不行变方针。

手写 Immer 中心源码

下面是 immer 库运用 Proxy 方针完成的中心源码:

// 界说一个生成递归署理方针的函数
function createNextLevelProxy(base, proxy) {
  return new Proxy(base, {
    get(target, prop) {
      // 假如读取的不是函数,且 prop 不是immer.proxies或immer.originals,则回来署理方针的相应特点
      if (prop !== "immer_proxies" && prop !== "immer_original") return proxy[prop];
      // 不然回来方针方针的相应特点
      return target[prop];
    },
    set(target, prop, value) {
      // 将之前署理的值存入 original 方针中,用于恢复状况
      const original = target[prop];
      // 在署理方针上运行修正操作,改动方针的状况
      proxy[IMMER_PROXY_MARKER].mutations.push({
        prop,
        value,
        original,
      });
      // 将新的值赋值到方针方针中
      target[prop] = value;
      // 回来新的署理方针
      return true;
    },
  });
}
// 界说 createProxy 函数,用于创立递归署理方针
function createProxy(base, parent) {
  //浅复制一个方针或数组
  const target = Array.isArray(base) ? base.slice() : { ...base };
  //简略界说
  const proxy = Object.defineProperty(
    {
      [IMMER_PROXY_MARKER]: {
        parent,
        target,
        drafted: false,
        finalizing: false,
        finalized: false,
        mutations: [],
      },
    },
    "immer_original",
    {
      value: base,
      writable: true,
      configurable: true,
    }
  );
  for (let prop in target) {
    if (isObject(target[prop])) {
      // 递归署理子方针
      target[prop] = createNextLevelProxy(target[prop], proxy);
    }
  }
  return proxy;
}

在这个完成中,咱们首要经过数组的 slice 办法或方针的扩展运算符创立了一个浅复制的方针,而且界说了一个新的署理方针 proxy。然后,咱们在署理方针上调用 Object.defineProperty 办法,为署理方针增加一个 immer_proxies 特点,这个特点包括了署理方针的 details 方针和一个默许值为 false 的 drafted 特点,用于记载这个方针是否现已被署理。 然后,咱们遍历target 方针上的一切特点,假如这些特点是一个方针的话,咱们递归地为它们创立署理方针(经过 createNextLevelProxy 函数完成)。最终,咱们回来新创立的署理方针

这样,咱们就完成了 immer 库方针的署理功能。在运用 immer 时,咱们能够经过 immer.produce 办法或许 immer 函数来创立一个方针的署理。在方针被署理后,咱们能够随意地对方针进行修正操作,这些修正操作将会被记载在署理方针的 details 方针的 mutations 数组中。当需求创立新的不行变方针时,咱们能够将署理方针中的修正操作应用于原始方针(即 immer_original 方针)。这样,咱们就能够依据原始方针和修正操作的历史记载生成一个新的不行变方针。

在 set 函数中,咱们还记载了修正前的值,并将其存储到署理方针的 details 方针中的 mutations 数组中,这样就能够在撤销修正时运用原始值。

createProxy 函数中,咱们将 immer_proxies 特点增加到署理方针上(并不是作为 getter),而且将这个特点的默许值设置为一个空方针。在创立结束署理方针后,咱们遍历方针的一切特点,并递归调用 createNextLevelProxy 函数来为一切方针特点创立署理方针。最终,咱们回来新创立的署理方针。

new 创立方针原理详解

创立方针的进程

运用关键字 new 能够在 JavaScript 中创立方针。当咱们运用 new 操作符时,它会执行以下进程:

  1. 创立一个新方针。
  2. 将新方针的原型设置为结构函数的 prototype 特点。
  3. 将新方针作为 this 方针调用结构函数。
  4. 假如结构函数回来一个方针,则回来该方针;不然回来这个新方针。

别的,结构函数内部运用 this 关键字来指代新创立的方针。

下面是一个示例代码:

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.sayHello = function() {
  console.log('Hello, my name is ' + this.name + ', and I am ' + this.age + ' years old.');
};
let person = new Person('John', 30);
person.sayHello(); // 输出 "Hello, my name is John, and I am 30 years old."

在创立 person 方针的进程中,new 操作符执行了以上四个进程:

  1. 创立了一个新方针 person。
  2. 将 person 的原型设置为 Person.prototype。
  3. 将结构函数 Person 的 this 方针设置为 person,并执行结构函数内部的代码。
  4. 因为结构函数 Person 没有回来值,所以回来的是新创立的方针 person。

需求留意的是,运用 new 进行方针创立时,若结构函数内部的 this 指向有问题,其或许会对大局效果域形成影响。 例如:

function Circle(radius) {
  this.radius = radius;
  this.getArea = function() {
    return Math.PI * this.radius ** 2;
  };
}
let circle = Circle(5); // 错误:没有创立一个新方针
console.log(circle); // 输出 undefined
console.log(radius); // 输出 5

在上面的代码中,Circle 函数内部短少 new 关键字,导致 this 无法指向新创立的方针,而是指向了大局效果域。 因而,一切的特点和办法都被界说在了大局效果域下,circle 变量没有被赋值为新方针,而是为 undefined。此刻,radius 变量的值被覆盖为 5。

手写一个 new

  1. 创立一个空方针,并将其原型设置为结构函数的 prototype 特点。
  2. 将该空方针作为 this 关键字调用结构函数。
  3. 假如结构函数回来一个方针,则回来该方针,不然回来 this 方针。

下面是一个示例代码:

function myNew(constructor, ...args) {
  let obj = Object.create(constructor.prototype);
  let res = constructor.apply(obj, args);
  return res instanceof Object ? res : obj;
}
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.sayHello = function() {
  console.log('Hello, my name is ' + this.name + ', and I am ' + this.age + ' years old.');
};
let person = myNew(Person, 'john', 30);
person.sayHello(); // 输出 "Hello, my name is john, and I am 30 years old."

在 myNew 函数中,咱们首要经过 Object.create() 办法创立了一个空方针 obj,并将其原型设置为结构函数的 prototype 特点。然后运用apply() 办法将结构函数的 this 指向该方针,并传递了参数 args。最终判别结构函数的回来值是否为一个方针,假如是,则回来该方针,不然回来创立的新方针 obj。这样就完成了一个简略的 new 函数。

需求留意的是,因为 new 操作符在运用时还会进行一些额外的处理,如设置该方针的 constructor 特点等,因而以上完成办法并不完好。可是,以上的完成办法能够协助咱们更好地了解 new 操作符的原理和完成进程。

面试常问

1.  简略说说 JavaScript 原型承继的概念
2.  怎样了解 prototype 和 __proto__