1.理解原型设计模式以及JS中的原型规则

原文地址:https://www.cnblogs.com/yy95/p/5751136.html
Person构造函数、Person原型对象、Person现有的两个实例之间的关系:
截屏2021-12-13 下午8.00.04.png
每一个构造函树都会有一个原型属性(prototype),它是一个指针,指向原型对象;默认情况下,原型对象会包含一个constructor(构造函数)属性(原型最初只包含constructor属性),这个属性包含一个指向prototype属性的指针;拿上面的图做例子,Person.prototype.constructor指向Person,通过这个构造函数就可以继续为原型对象添加其他的属性和方法。
当调用构造函数创建一个新实例后,该实例内部会包含一个内部属性(指针),它指向构造函数的原型对象;这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间;也就是说这个内部属性和构造函数没有直接的关系。
原型链是实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
每个构造函数都有一个prototype属性,指向原型对象。原型对象都包含一个指向构造函数的指针(constructor)。每个实例对象( object )都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( proto ) ,层层向上直到一个对象的原型对象为 null。

2.instanceof的底层实现原理,手动实现一个instanceof

A instanceof B :A是否是B的实例,可以用来判断js变量类型(引用类型,基础了类型的undefined,null,不能判别,因为没有constructor属性)
instanceof 的原理:
从当前引用的proto一层一层顺着原型链往上找,能否找到对应的prototype。找到了就返回true。
左边实例的proto属性指向他构造函数的原型对象,只要顺着proto属性找,依次和右边构造函数的prototype属性对比相同,(构造函数的prototype也指向原型对象),所以只要相同,说明有A是B的实例
实现instanceof

  1. function instance_of(L, R) { //L 表示左表达式,R 表示右表达式
  2. const O = R.prototype. // 拿到显示原型
  3. L = L.__proto__ // 拿到隐式原型
  4. while(true) { // 当 显示原型 严格等于 隐式原型时 返回true
  5. if (L === null) return false;
  6. if (O === L) return true;
  7. L = L.__proto__; // 获取祖类型的__proto__
  8. }
  9. }

4.实现继承的几种方式以及他们的优缺点

原文地址: https://www.jianshu.com/p/90f95be9236a

5.至少说出一种开源项目(如Node)中应用原型继承的案例

new一个对象的详细过程:

  1. function Test(){}
  2. const test = new Test()

Vue.extend( options )

  • 参数
    • {Object} options
  • 用法

使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
data 选项是特例,需要注意 - 在 Vue.extend() 中它必须是函数

  1. <div id="mount-point"></div>
  2. // 创建构造器
  3. var Profile = Vue.extend({ template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  4. data: function() {
  5. return {
  6. firstName: 'Walter',
  7. lastName: 'White',
  8. alias: 'Heisenberg'
  9. }
  10. } })
  11. // 创建 Profile 实例,并挂载到一个元素上。
  12. new Profile().$mount('#mount-point')

结果如下:

Walter White aka Heisenberg

为什么使用 extend

在 vue 项目中,我们有了初始化的根实例后,所有页面基本上都是通过 router 来管理,组件也是通过 import 来进行局部注册,所以组件的创建我们不需要去关注,相比 extend 要更省心一点点。但是这样做会有几个缺点:

  1. 组件模板都是事先定义好的,如果我要从接口动态渲染组件怎么办?
  2. 所有内容都是在 #app 下渲染,注册组件都是在当前位置渲染。如果我要实现一个类似于 window.alert() 提示组件要求像调用 JS 函数一样调用它,该怎么办?
    这时候,Vue.extend + vm.$mount 组合就派上用场了。

6.可以描述new一个对象的详细过程,手动实现一个new操作符

先看看 new 操作符都干了什么事情,有哪些操作?
1.创建一个空对象(也可以说是从堆内存中开发一块内存)
2.将构造函数的作用域赋值给新对象,改变this指向,把构造函数的this指向新对象,并执行构造函数的函数体。(谁是new出来的对象,this就指向谁,下面代码中this会指向 14行的person )
let
3、设置新对象的proto属性指向构造函数的原型对象
obj.proto = Person.prototype;
4.新构造的对象作为new运算符的返回值(return {})

构造函数的特性

  • 如果构造函数返回一个对象,则该对象将作为整个表达式的值返回,而传入构造函数的this将被丢弃
  • 但是,如果构造函数返回的是非对象类型,则忽略返回值,返回新创建的对象
  1. // 新建一个类(构造函数)
  2. function Otaku(name, age) {
  3. this.name = name
  4. this.age = age
  5. // 自身的属性
  6. this.habit = 'pk'
  7. }
  8. // 给类的原型上添加属性和方法
  9. Otaku.prototype.strength = 60
  10. Otaku.prototype.sayYourName = function() {
  11. console.log('I am ' + this.name)
  12. }
  13. // 实例化一个person对象
  14. const person = new Otaku('乔峰', 5000)
  15. person.sayYourName() // I am 乔峰 ,执行实例相当于执行构造函数中的代码
  16. console.log(person) // 打印出构造出来的实例

解析

从控制台打印出来的结果我们可以看出 new 操作符大概做了一下几件事情:

  1. 返回(产生)了一个新的对象
  2. 访问到了类 Otaku 构造函数里的属性
  3. 访问到 Otaku 原型上的属性和方法 并且设置了 this 的指向(指向新生成的实例对象)

通过上面的分析展示,可以知道 new 团伙里面一定有 Object 的参与,不然对象的产生就有点说不清了。 先来边写写:

  1. // 需要返回一个对象 借助函数来实现new操作
  2. // 传入需要的参数: 类 + 属性
  3. const person = new Otaku('乔峰', 5000)
  4. const person1 = objectFactory(Otaku, '鸠摩智', 5000)
  5. // 开始来实现objectFactory 方法
  6. function objectFactory(obj, name, age) {}
  7. // 这种方法将自身写死了 如此他只能构造以obj为原型,并且只有name 和 age 属性的 obj
  8. // 在js中 函数因为arguments 使得函数参数的写法异常灵活,在函数内部可以通过arguments来获得函数的参数
  9. function objectFactory() {
  10. console.log(arguements)
  11. //{ '0': [Function: Otaku], '1': '鸠摩智', '2': 5000 }
  12. // 通过arguments类数组打印出的结果,我们可以看到其中包含了构造函数以及我们调用objectfactory时传入的其他参数
  13. // 接下来就是要想如何得到其中这个构造函数和其他的参数
  14. // 由于arguments是类数组,没有直接的方法可以供其使用,我们可以有以下两种方法:
  15. // 1. Array.from(arguments).shift();
  16. //转换成数组 使用数组的方法shift将第一项弹出
  17. // 2. [].shift().call(arguments);
  18. // 通过call() 让arguments能够借用shift方法
  19. const Constructor = [].shift.call(arguments)
  20. const args = arguments
  21. // 新建一个空对象 纯洁无邪
  22. let obj = new Object()
  23. // 接下来的想法 给obj这个新生对象的原型指向它的构造函数的原型
  24. // 给构造函数传入属性,注意:构造函数的this属性
  25. // 参数传进Constructor对obj的属性赋值,this要指向obj对象
  26. // 在Coustructor内部手动指定函数执行时的this 使用call、apply实现
  27. let result = Constructor.apply(obj, arguments)
  28. //确保new出来的是一个对象
  29. return typeof result === 'object' ? result : obj }
  • 上面的代码注释太多,剔除注释以后的代码:

    1. function objectFactory() {
    2. let Constructor = [].shift.call(arguments)
    3. const obj = new Object()
    4. obj.__proto__ = Conctructor.prototype
    5. let result = Constructor.apply(obj, arguments)
    6. return typeof result === 'object' ? result : obj
    7. }
  • 还有另外一种操作:

    1. function myNew(Obj, ...args) {
    2. var obj = Object.create(Obj.prototype)
    3. // 使用指定的原型对象及其属性去创建一个新的对象
    4. Obj.apply(obj, args)
    5. // 绑定 this 到obj, 设置 obj 的属性
    6. return obj
    7. // 返回实例
    8. }

7.理解es6 class构造以及继承的底层实现原理