资讯专栏INFORMATION COLUMN

关于原型、原型链、和继承

沈俭 / 861人阅读

摘要:根据定义,没有原型,并作为这个原型链中的最后一个环节。偷偷贴在这里再知道了原型原型链,那一个对象的过程知道吗,能手写一个吗。

似乎生活中常常会遇到这种情况,你去一家公司面试,前面面的都挺好,你觉得你对基础算法的了解很好,各种排序,红黑树,二叉树,深度/广度优先算法都答出来了,leetcode上的若干困难题目也都答上来了,然后面试官说,"那么好吧,介绍一下你对原型的看法吧。"

???我头发。我leetcode上刷了100天,我费劲心思研究了各种算法和数据结构,你叫我讲讲原型?

为了应对这种情况,本文通过如下代码展示下js的原型仅供参考。

上CODE
const c = (...v) => console.log(...v);
function People(name, age) {
  this.name = name;
  this.age = age;
}
/*你想这么写也没关系
const People = function(name, age) {
  this.name = name;
  this.age = age;
}
*/
People.prototype.info = function() {
  c(`My name is ${this.name}, my age is ${this.age}.`);
};  // 在原型上定义方法
function Student(name, age, school) {
  People.call(this, ...arguments); 
  this.school = school;
}
Student.prototype = People.prototype;  
// 这里推荐用new People(),直接指定People.prototype会污染People原型,如下结果
Student.prototype.constructor = Student;  //修正constructor指向
Student.prototype.talk = function() {
  c(`My school is ${this.school}`);
};  // 需要在改变了Student的原型后定义,否则无法获取到该方法

const xiaoD = new Student("xiaoD", 4, "小星星幼儿园");
xiaoD.info();
xiaoD.talk();
const somebody = new People("somebody", 22);
somebody.talk();
c(xiaoD.__proto__ === Student.prototype);
c(Student.__proto__ === Function.prototype);
c(Function.prototype === Function.__proto__);
c(Function.__proto__ === Object.__proto__);
c(Object.__proto__ === Function.prototype);
c(Object.prototype === Function.prototype.__proto__);
c(Object.prototype.__proto__ === null);

可以先猜一下是什么结果。。

好吧,不用猜了。结果如下

My name is xiaoD, my age is 4.
My school is 小星星幼儿园
My school is undefined
true
true
true
true
true
true
true

每个实例对象( object )都有一个私有属性(称之为 proto )指向它的原型对象( prototype )。该原型对象也有一个自己的原型对象( proto ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。至于谁指谁,应该从上面的代码中就可以清晰的看出来了。这里注意的是只有函数中才有 prototype 属性。

类的继承
class People {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  info() {
    c(`My name is ${this.name}, my age is ${this.age}.`);
  }
}
class Student extends People {
  constructor(name, age, school) {
    super(...arguments);        // 继承属性
    this.school = school;
    this.talk = this.talk.bind(this);        // 绑定this
    /* 或者这样绑定
      this.talk = () => {
      this.info();  // 箭头函数中的this在定义时绑定
      c(`My school is ${this.school}`);
    };
      */
  }
  talk() {
    this.info();
    c(`My school is ${this.school}`);
  }
  
}

const xiaoD = new Student("xiaoD", 4, "小星星幼儿园");
xiaoD.talk();
const { talk } = xiaoD;
talk();        // 不绑定this这里会报错
const somebody = new People("somebody", 22);
somebody.talk();  // 报错,父类中没有该方法

这里有三个注意点:

父类中不会存在子类的方法(面向对象六大原则还记得吗,开闭,单一职责,依赖倒转,里氏置换,知道最少,接口隔离,合成聚合复用)

上面代码中,talk方法中的this,默认指向Student类的实例。但是,如果将这个方法提取出来多带带使用,this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined),从而导致找不到info方法而报错。解决办法如代码中所示。

关于箭头函数的this指向问题,众所周知,普通函数的this是指向调用它的那个对象,所以普通函数可以通过apply,call,bind来改变this的指向。而箭头函数中的this是在定义函数的时候就已经确定了,指向外层作用域链中的普通函数,若没有,this则指向undefined。

PS

(那么问题又来了,手写个apply,call,bind的polyfill吧)

const obj = {
  name: "xiaoD"
};
const fn = function(...args) {
  c(this.name, ...args);
};
Function.prototype.myApply = function(obj, [...args]) {
  return this.call(obj, ...args);
};
fn.myApply(obj, ["真", "棒"]);        // xiaoD 真 棒
fn.apply(obj, ["真", "棒"]);            // xiaoD 真 棒

拿走,不谢。

Function.prototype.myApply = function(obj, [...args]) {
  obj.fn = this;
  let ret = obj.fn(...args);
  delete obj.fn;
  return ret;
}; 
Function.prototype.myBind = function(obj) {
  obj.fn = this;
  return function(...args) {
    return obj.fn(...args);
  };
};
// 偷偷贴在这里
再PS

知道了原型、原型链,那new一个对象的过程知道吗,能手写一个吗。

new一个对象的过程大概分成三步:

新建一个空对象

改变原型链指向,调用构造函数

返回这个新对象

const myNew = function(fn, ...args) {
  let obj = {};
  obj.__proto__ = fn.prototype;
  let ret = fn.call(obj, ...args);
  return ret ? ret : obj;
};
function People(name, age) {
  this.name = name;
  this.age = age;
}
let xiaoD = myNew(People, "xiaoD", 4);
let xiaoY = new People("xiaoY", 4);            // 可以对比一下,看看两者区别

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109276.html

相关文章

  • 关于构造函数、原型原型、多种方式继承

    摘要:可以看出这个构造函数是由创建出来的,而我们看下的隐式原型,竟然是指向了的原型,也就是也是由创建出来的。例如,其他构造函数的原型将覆盖属性并提供自己的方法。将构造函数的执行对象赋给这个空对象并且执行。把对象的隐式原型指向构造函数的原型。 构造函数与实例对象 又是这个经典的问题,嗯,我先来写个构造函数,然后实例化一个对象看看。 function Person(name) { this....

    zxhaaa 评论0 收藏0
  • 关于javascript中的原型原型

    摘要:先来一个构造函数构造一个人类实例化一个对象看看的名字是什么打印结果先说一个前提只要是函数,就会有一个属性,可以理解为子代的原型遗传基因只要是对象,就会有一个方法,可以理解为向上寻找原型的方法。 关于javascript中的原型和原型链 我GitHub上的菜鸟仓库地址: 点击跳转查看其他相关文章 文章在我的博客上的地址: 点击跳转         关于javascript中的原型和原...

    SmallBoyO 评论0 收藏0
  • 原型是什么?关于原型中constructor、prototype及__proto__之间关系的认

    摘要:的隐式原型是母,母是由构造函数构造的,但函数的隐式原型又是。。。。可能是考虑到它也是由构造函数生成的吧,所以返回的值也是。 showImg(https://segmentfault.com/img/bVyLk0); 首先,我们暂且把object类型和function类型分开来,因为 function是一个特殊的对象类型,我们这里这是便于区分,把function类型单独拿出来。顺便一提,...

    kaka 评论0 收藏0
  • 【面试系列】之二:关于js原型

    摘要:之二关于原型开篇我记得初学时,最难懂的概念就是的原型,而且这个概念在笔试面试中常常提到,因此今天我们把这个概念拿出来,好好聊一聊。 之二:关于js原型 1. 开篇 我记得初学js时,最难懂的概念就是js的原型,而且这个概念在笔试面试中常常提到,因此今天我们把这个概念拿出来,好好聊一聊。 在仔细讲解之前,我们先来看一道题,这道题来自JavaScript高级程序设计中原型链那一节: fun...

    _ang 评论0 收藏0
  • 详解js中的继承(二)

    摘要:寄生组合式继承终于写到最后一个继承了,我们在之前讲了种继承方式,分别是原型链,借用构造函数继承,组合继承,原型式继承,寄生式继承,其中,前三种联系比较紧密,后面两种也比较紧密,而我们要讲的最后一种,是和组合继承还有寄生式继承有关系的。 前言 趁周末结束之前赶紧先把坑填上。上回我们说到了原型链,并且留下了几个思考题,先把答案公布一下。 在最后一个例子里,console.log(b1.c...

    evin2016 评论0 收藏0
  • 关于javascript的原型原型,看我就够了(二)

    摘要:原文链接关于的原型和原型链,看我就够了一参考链接闯关记之原型及原型链之原型与原型链一篇文章带你理解原型和原型链彻底理解原型链一的默认指向图解和的三角关系原型和原型链三张图搞懂的原型对象与原型链 温故 创建对象的三种方式 通过对象直接量 通过new创建对象 通过Object.create() js中对象分为两种 函数对象 普通对象 仔细观察如下代码 function Foo(na...

    eccozhou 评论0 收藏0

发表评论

0条评论

沈俭

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<