Lambda博客
  • 自我介绍
  • SSR
    • 浅谈SSR
  • React
    • 页面路由——React-Router
    • 管好你的状态——React-Redux
    • 一个哪行——React详解
    • 左膀右臂一个不少——React初探
  • 问题记录
    • js-xlsx实现纯前端导出excel表格
    • 前端开发需要了解的东西
    • 打造高性能网站
  • JavaScript学习笔记
    • 语法和API
    • js-垃圾回收
    • 收集的JS使用技巧
    • 执行机制
    • 原型和原型链
    • 执行上下文
    • 事件循环
    • JavaScript手写代码
    • 43道JS面试题
    • 悄悄的带走了你的东西——闭包详解
    • 你是个富二代吗?——JavaScript作用域链
    • 捉摸不透的女生——JavaScript类型转换
    • 这是你的女神!——JavaScript
  • 网络学习笔记
    • 强不强——HTTP-协商缓存VS强缓存
    • 网络缓存
    • 我要飞的更高——计算机网络
    • 快点!再快点!——CDN
    • 喜怒哀乐多状态——HTTP状态码
    • 你会爱上我的(ಥ_ಥ) ——TCP详解
    • 隔壁老王想篡位?门都没有——同源策略
  • 软技能
  • 安全专题
    • 保护好自己——网站安全,预防
  • 浏览器兼容性
    • 我是个好人——浏览器兼容性
  • 多图片加载——懒加载详解
  • 数据结构
  • Node
    • Node初探
  • Typescript
    • JS Plus 真香——初探 TypeScript
  • 设计模式
    • 不要花里胡哨,要一套一套——设计模式(1)
    • 学会“套路”——设计模式(2)
  • Vue
    • 实操实干——vue实例记录
    • 停下来,问问自己——Vue-刨根问底
    • 你有喘息的机会吗?——Vue,逐步了解
    • 你累吗?来来来,安利框架——Vue-初次见面
  • 小程序
    • 今天天气怎么样——记一次小程序开发
  • Webpack
    • 蛋糕分割整合工具——Webpack-前端工程化
  • 你一块,我一块——Web-modules 前端模块化
  • HTML5
    • 你会画小猪佩奇吗?—— canvas
    • 画个矢量图——HTML5-SVG
    • 听说你爱闹腾——HTML5-多媒体
    • 动画神器——HTML5-requestAnimationFrame
    • 留下痕迹——HTML5-客户端存储
    • 你知道指北针吗?——HTML5-DeviceMotionEvent
    • 你在哪啊?我在这啊——HTML5-Geolocation
    • 你到这,你到那——HTML5-拖拽
    • 你从这,到那——HTML5-拖拽上传文件
    • 开启前端之路——HTML 标签
  • 瀑布流(无限滚动)
  • 我是怎么来的?——浏览器渲染原理
  • Css
    • 由大变小,你行吗 —— 移动端
    • 动起来,这样比较炫—— CSS3 动画
    • 请不要拐弯抹角 —— CSS3 选择器
    • 想炫吗?—— CSS3 属性
    • 最后的最后——CSS自问自答
    • 万事开头难?——深入学习前端常见布局
    • 一入前端深似海——BFC剖析
    • 还有哪些好玩的东西——CSS提升
    • 那些好看的页面是怎么形成的——CSS 初识
  • 拿个小本本记下——Cookie
由 GitBook 提供支持
在本页
  • 理解原型设计模式以及 JavaScript 中的原型规则
  • instanceof 的底层实现原理,手动实现一个 _instanceof
  • 实现继承的几种方式以及他们的优缺点
  • new 一个对象的详细过程,手动实现一个 new 操作符
在GitHub上编辑
  1. JavaScript学习笔记

原型和原型链

理解原型设计模式以及 JavaScript 中的原型规则

什么是原型模式

原型模式(Prototype pattern):通俗点讲就是创建一个共享的原型,并通过拷贝这些原型创建新的对象。 用于创建重复的对象,这种类型的设计模式属于创建型模式,它提供了一种创建对象得比错选择。

实现原型模式

我们可以通过 JavaScript 特有的原型集成特性去实现原型模式,也就是创建一个对象作为另一个对象的 prototype 属性值,我们可以通过 Object.create(prototype,optionalDescriptorObjects)来实现原型继承。

var fatherPrototype = {
  init: function(money) {
    this.selfMoney = money;
  },
  getMoney: function() {
    console.log("取钱" + this.selfMoney);
  }
};

function sonMoney(Smoney) {
  function F() {}
  F.prototype = fatherPrototype;
  var f = new F();
  f.init(Smoney);
  return f;
}

var sonGetMoney = sonMoney("20");
sonGetMoney.getMoney();
//儿子取钱取得是他爹的

原型模式,就是创建一个共享的原型,通过拷贝这个原型来创建新的类,用于创建重复的对象,带来性能上的提升。

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

先来看个例子

function Person(name, age) {
  this.name = name;
  this.age = age;
}

function Student(score) {
  this.score = score;
}

Student.prototype = new Person("李明", 22);
var s = new Student(99);

console.log(s instanceof Student); //true
console.log(s instanceof Person); //true
console.log(s instanceof Object); //true

检测对象 A 是不是另一个对象 B 的实例的原理:查看对象 B 的 prototype 属性指向的原型对象是否在对象 A 的原型上,若在则返回 true,若不在则返回 false。

function _instanceof(ins, cons) {
  while (true) {
    if (ins.__proto__ === null) {
      return false;
    }
    if (ins.__proto__ === cons.prototype) {
      return true;
    }
    ins = ins.__proto__;
  }
}

_instanceof 实际上是根据原型链向上追溯,子类找不到找父类,直到 ins.__proto__指向的就是 cons.prototype,否则,当 ins 已经追溯到 Object.prototype,由于 Object.prototype 是没有__proto__的,所以当 ins.proto === null 时,就说明我们的循环到最后也没有成功使 ins.proto === cons.prototype,那么 ins 就不是 cons 或 cons 的子类实例化出来的对象,函数返回 false。

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

  • 构造继承

  • 原型继承

  • 实例继承

  • 拷贝继承

下面谈一下前三种的继承方式

function Father(){}
function Son{}

//方法一
Son.prototype = new Father();

//方法二
Son.prototype = Father.prototype;

//方法三
Son.prototype = Object.create(Father.prototype);

  • 方法一:

    • 优点:正确设置原型链实现继承

    • 优点:父类实例属性得到继承,原型链查找效率提高,也能为一些属性提供合理的默认值。

    • 缺点:父类实例属性为引用类型时,不恰当的修改会导致所有子类被修改。

    • 缺点:创建父类实例作为子类原型时,可能无法正确构造函数需要的合理参数,这样提供的参数继承给子类没有实际的意义,当子类需要这些参数时应该在构造函数中进行初始化设置。

    • 总结:继承应该是继承方法而不是继承属性,为子类设置父类实例属性应该是通过在子类构造函数中调用父类构造函数进行初始化。


  • 方法二:

    • 优点:正确设置原型链实现继承;

    • 缺点:父类构造函数原型与子类相同。修改子类原型添加方法会修改父类。


  • 方法三:

    • 优点:正确设置原型链且避免方法 1-2 中的缺点

    • 缺点:ES5 方法需要注意兼容性。


  • 改进

    • 所有三种方法应该在子类构造函数中调用父类构造函数实现实例属性初始化。

    function Son() {
      Father.call(this);
    }
    • 用新创建的对象替代子类默认原型,设置Son.prototype.constructor = Son;保证一致性;

    • 第三种方法的延伸版本

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

new 一个对象的详细过程,手动实现一个 new 操作符

new运算符创建一个用户定义的对象类型的实例。 使用new命令时,它后面的函数依次执行下面的步骤。

  • 创建一个空对象,作为将要返回的对象实例。

  • 将这个空对象的原型,指向构造函数的prototype属性。

  • 将这个空对象赋值给函数内部的this关键字。

  • 开始执行构造函数内部的代码。

也就是说,构造函数内部,this指的是一个新生成得空对象,所有针对this的操作,都会发生在这个空对象上。构造函数之所以叫”构造函数”,就是说这个函数的目的,就是操作一个空对象(即this对象),将其”构造”为需要的样子。

new命令简化的内部流程:

/**
 *
 * @param {*} constructor 构造函数
 * @param {*} params 构造函数参数
 */

function _new(constructor, params) {
  // 将arguments对象转化为数组
  var args = [].slice.call(arguments);
  // 取出构造函数
  var constructor = args.shift();
  // 创建一个空对象,继承构造函数的prototype属性
  var context = Object.create(constructor.prototype);
  // 执行构造函数
  var result = constructor.apply(context, args);
  // 如果返回结果是对象,就直接返回,否则返回context对象
  return typeof result === "object" && result != null ? result : context;
}

var actor = _new(Person, "张三", 28);
上一页执行机制下一页执行上下文

最后更新于3年前