✨
blog
  • Blog
  • Element-UI
    • 2019-09-04
  • JS
    • ES6 之 Set 和 Map
    • let 和 const 声明常见概念
    • 元编程
    • ES6之字符串的扩展
    • ES6 之异步流程的前世今生(上)
    • ES6之异步流程的前世今生(下)
    • ES6 之模块你知吗
    • ES6 之解构赋值与箭头函数的妙用
    • 迭代器
    • ES5 之原型(一)
    • ES6之类(二)
    • es7之装饰器
    • es6之数组详解
    • js之this指向
    • 对象
    • vue项目配合使用canvas联动
    • 本文解决痛点:对象里面是否有值
  • MAC
    • vue源码之method
    • Mac的使用技巧
    • 前文
    • Mac常用软件(二)
    • 如何查看 Mac 端口号以及占用情况
  • Node
    • Node之Buffer详解
    • 浏览器与 node 的事件循环(event loop)有何区别
    • Node之多线程
    • node之模块解析(一)
    • 错误捕获与内存告警
  • TS
    • Record
    • 使用方法
    • 工具泛型
    • 类型体操
    • 泛型
  • chrome
    • v8 引擎
    • v8 垃圾回收机制
    • 浏览器的知识
  • flutter
    • 路由
    • 页面布局
  • go
    • index
  • html&css
    • 两栏布局
    • ES5和ES6的区别
    • ES5 和 ES6 的区别
    • HTTP详解
    • TCP 与 UDP 的区别
    • MDN
    • css modules 使用教程
    • css 居中
    • 拖拽
    • flex布局
    • h5 新增特性 html5
    • history 与 hash 路由策略
    • position 定位方式
    • rem布局
    • svg
    • web性能优化
    • 事件循环
    • 从输入网址后发生了什么
    • 前端状态管理
    • 圣杯布局与双飞翼
    • 性能优化 页面的性能统计指标
    • 本地存储的几种对比
    • 浏览器的渲染进程
    • 浏览器缓存策略详解
    • 盒模型
    • 为什么要移动端适配
    • 跨域的 N 种实现方式
  • web3
    • 常见概念
    • vue项目配合使用canvas联动
  • webgl
    • Mac使用技巧(二)
    • Node之模块解析path
  • 代码库
    • documeng的一些常见操作
    • eventBus事件
    • jquery提交
    • jquery的一些常见操作
    • 常见操作
    • 数组polyfill
    • TS代码片段
      • 面试官眼中的test unit
  • 全年安排
    • AfterShip
    • 大企业
  • 函数编程题
    • Promise问题
    • 继承
  • 前端早早聊
    • vue生态
    • 开发一款VScode语言插件
    • 简历回顾和进行复盘
    • 重新认知性能优化及其度量方法
    • 2022-09-17-音视频专场.md
      • 2022-09-17-音视频专场
    • 前端晋升专场
      • 成长的诀窍是靠自己
      • 销销帮
    • 前端监控专场
      • 字节前端监控实践
      • 李港:大前端-从无到有搭建自研前端监控系统
    • 前端跳槽
      • 50个面试官在线招聘
      • 如何识别优秀的前端猎头来跳槽大厂
      • 面试套路
    • 支付宝
      • 面试
    • 管理专场
      • 芋头:管理者眼中的web技术发展前沿
    • 组件专场
      • 基于webCompents的跨技术组件库实践
    • 面试
      • 面试辅导问题
      • 早早聊面试
      • 前端沙箱是什么? 怎么实现沙箱环境?
  • 常见总结
    • 2018年终总结-年底了,你总结了吗?我先来
    • 在逆境中成长
    • 2021年终总结
    • 2024年全年总结
    • 项目
    • Tell2.0 前端复盘
    • 复盘
    • 前端工程师素养
    • 学习方法论
    • 希望与破晓| 2022 年终总结
    • 新起点, 新征途 | 掘金年度征文
    • 稳定| 2023 年终总结
    • 趁着有风快飞翔 | 2019 年终总结
    • AfterShip
      • Emotion:用 JavaScript 编写 CSS 的强大工具
      • 个人中长期目标
      • 事故复盘
      • 时间解析
      • 国内外区别
      • 独立站建设
    • MEIZU
      • NativeApp与H5通信原理
      • SSR 原理
      • SSR的常见问题
      • CLI
      • electron 应用发布流程
      • electron
      • electron 面试
      • 数据结构与算法之美
      • mgc 一期复盘
      • 架构原理
      • 喵币管理
      • 三期复盘总结
      • 异常监控之 sentry 实践
      • 微前端
      • qiankun 原理解析
      • 快游戏一期
      • 游戏中心复盘
    • 个人准则
      • index
    • 编程猫
      • pc 接入 micro bit 方案
      • prompt engineer
      • web work 跨域解析与解决方式
      • web 中的 ai
      • 低版本 node 环境下 ffmpeg 的使用
      • 关于 taobao 源 https 过期
      • 加密 json
      • 安卓 5 和 6 的白屏解决
      • 性能排查与优化实践
      • 探月接入
      • 接入硬件
      • 新生态下的state
      • monorepo 包管理方式
      • 自修复 npm 库
      • 音频的绘制
    • 谨启
      • 音视频
      • 小程序
        • taro 规范
        • 结合 mobx 在跳转前预请求
        • Taro 浅析用法与原理
        • 前文
        • 小程序优化指南
        • 小程序内部实现原理
        • 支付相关
    • tencent
      • TAPD
        • MathJax的食用
        • canvas渲染优化策略
        • 为什么 JavaScript 是单线程的呢?
        • svg 总是不对
        • 前端库
        • 原生端和js端如何通信
        • 在旧项目中复用vue代码
        • 提升自我
        • 批量编辑优化
        • 插入业务对象
        • 编辑器
        • 挂载点
        • 性能优化对比
        • 遇到的问题
        • 项目迁移公告
        • 领导力
      • 行家
        • 实战篇
        • 职业发展、领导力、个人成长
        • 高质量沟通
  • 慕课网
    • react-native原理
    • react-native学习
  • 杂文
    • Dom 节点变动检测并录制的简单实现
    • 错误监控&错误捕获
    • NextJS与NuxtJS
    • 负载均衡的几种常用方式
    • PM2
    • service worker 控制网络请求?
    • SSL 和 TLS 的区别
    • Babel 你太美
    • echart踩坑经验
    • keyup、keydown你都知道有什么区别吗
    • 常见概念
    • 首屏加载优化与性能指标分析
    • preload 和 prefetch 的详解
    • 在项目中配置这几个关系
    • roullp 解析
    • tinymce原理浅析
    • wasm 在前端的应用
    • websocket
    • webworker
    • 项目
    • 从 ajax 到 axios
    • 从postcss 到自己开发一款插件
    • 从输入浏览器到页面展示涉及的缓存机制
    • 代码整洁之道
    • 你知道什么是aop吗
    • 函数式编程
    • 函数式编程指南
    • 前端input框文字最大值
    • 攻坚战
    • 前端书写 sdk
    • 前端文字转语音播放
    • 前端领域的 Docker 和 Kubernetes
    • 前端安全
    • 前端进阶之内存空间
    • 前端音频浅析
    • 十分钟搞定多人协作开发
    • 字符串的比较
    • 尾递归
    • 前文
    • 常见的算法可以分为以下三类
    • 手机调试--mac篇
    • 数组的原生系列
    • COOP 和 COEP - 新的跨域策略
    • 浅谈react组件书写
    • 浏览器与 Node.js 事件循环的区别
    • 由三道题引伸出来的思考
    • 移动端300ms点击延迟
    • 移动端和pc端事件
    • Git 常见疑惑
    • 我们离发 npm 包还有多远
    • 重绘和重排
    • AI 时代下的前端编程范式
    • 音频可视化实战
  • 极客时间
    • Serverless入门课
    • 二分查找
    • 二叉树
    • 全栈工程师
    • 动态规划面试宝典
    • 前端与rust
    • 散列表
    • 前端方面的Docker和Kubernetes
    • 栈
    • 深入浅出区块链
    • 玩转 vue 全家桶
    • 玩转 webpack
    • 程序员的个人财富课
    • 算法
    • 说透元宇宙
    • 跳表
    • 链表
    • 10x 程序员工作法
      • index
    • Node开发实战
      • HTTP服务的性能测试
      • JavaScript语言精髓与编程实战
      • 什么是node。js
      • svg精髓
    • ReactHooks核心原理与实战
      • ReactHooks核心原理与实战
    • Rust
      • Rust编程第一课
      • 前置篇
      • 深度思维
      • 重构
      • 类型体操
      • 基础知识
    • WebAssembly入门课.md
      • 基础篇
      • SSR的注水和脱水
      • jsBriage通信原理
      • 基础知识篇
    • 互联网的英语私教课
      • 互联网人的英语私教课
    • 代码之丑
      • 代码之丑
    • 前端全链路优化实战课
      • 网页指标
    • 图解 Google V8
      • 图解 Google V8
    • 浏览器工作原理与实践
      • 浏览器工作原理与实践
    • 算法面试通关 40 讲
      • 算法面试通关40讲
    • 跟月影学可视化
      • index
    • 软件设计之美
      • 软件设计之美
    • 重学前端
      • js
  • 后续的文件增加都会增加到上面并以编号对应
    • 1029. 两地调度
    • 151.翻转字符串里的单词
    • 2022.3.15
    • 前端数据结构
    • 前端常见算法
    • 前端常见排序
    • 恢复一棵树
  • 设计模式
    • 前端常见设计模式之MVC与MVVM
    • 前端之代理模式
    • 前端常见设计模式之单例模式
    • 前端常见设计模式之发布订阅模式
    • 前端之工厂模式
    • 观察者模式
    • 前端常见设计模式之适配器模式
  • 译文
    • [译] 如何使用CircleCI for GitHub Pages持续部署
    • 您是否优化了 API 的性能
    • [译][官方] Google 正式发布 Flutter 1.2 版本
    • 什么是 Deno ,它将取代 NodeJS ?
  • 读后感
    • JavaScript二十年
    • 1368个单词就够了
    • js编程精解
    • labuladong 的算法小抄
    • lodash常用方法
    • vue的设计与实现
    • 所有的静态资源都是get请求
    • 人生
    • 人生护城河
    • 你不知道的JavaScript
    • 前端核心知识进阶
    • 华为工作法
    • 反脆弱
    • 好好学习
    • 左耳听风
    • 摩托车维修之道
    • 数学之美
    • 深入理解svg
    • 浏览器的ESM到底是啥
    • 经济学原理
    • 编程珠玑
    • 防御式 css 精讲
    • 韭菜的自我修养
  • 雪狼
    • 2022-07-17
    • 基础知识
    • 阶一课程
      • 实战辅导一
      • 实战辅导二
  • 嵌入式
    • 树莓派
      • 排序
  • 源码
    • React
      • 核心知识点
      • errorBoundaries
      • immutable.js 的实现原理
      • React.Suspense
      • react源码分析之Fiber
      • batchedUpdate
      • Component
      • Context
      • react 源码分析之 diff 算法
      • React 中的 key 属性:原理、使用场景与注意事项
      • 使用方式
      • react源码分析之memo
      • react 源码分析之mixin
      • 实战篇
      • react源码分析之react-dom
      • 使用方式
      • scheduleWork
      • useImperativeHandle的使用与原理
      • React 书写小技巧
      • 入口和优化
      • 合成事件和原生事件的区别
      • react 性能优化
      • 构建一个 hooks
      • 浅析 styled-components
      • 生命周期
      • 组合 vs 继承
      • 通信机制
      • 高阶组件
      • 慕课网
        • 应用篇
        • 课程导学
    • ReactHook
      • useCallback
      • useContext
      • useEffect 与 useLayoutEffect
      • useHook
      • useMemo
      • useReducer
      • 原理
      • useState
      • 总结
    • Redux
      • mobx 原理解析
      • redux-saga
      • redux-thunk
      • Mobx 和 Redux 对比
      • 使用方法
      • redux 原理
    • Vite
      • Vite原理
      • Vite配置
      • 热更新原理
      • vite 为什么生产环境用 Rollup
    • Webpack
      • PostCSS
      • Webpack5 核心原理与应用实践-loader
      • Webpack5 核心原理与应用实践-plugin
      • Webpack5 核心原理与应用实践
      • 区分
      • 升级详情
      • treeShaking(树摇Tree Shaking)
      • 编写一个自己的webpack插件plugin
      • 代码分离(code-splitting)
      • webpack 打包优化
      • 基础配置
      • webpack 打包优化
      • webpack 工作原理
      • webpack 按需加载原理
      • webpack 热更新 HMR(Hot Module Replacement)
      • 缓存
      • webpack 自定义 plugin
    • next
      • tailwind
      • 什么是水合
    • sveltejs
      • index
    • tinymce
      • 并发篇
    • 源码手写系列
      • create
      • call
      • bind
      • call
      • es6 单例
      • forEach vs Map
      • instanceOf
      • new
      • reduce
      • 取两个重复数组的交集
      • 函数柯理化
      • 动态规划
      • 基于Generator函数实现async
      • 新建 js 文件
      • 手写一个 slice 方法
      • 手写一个 webpack loader
      • Plugin
      • 手写一个寄生组合式继承
      • 二叉树
      • 链表相关的操作
      • 手动实现发布订阅
      • 数组去重
      • 数组扁平化
      • 数组
      • 构造大顶堆和小顶堆
      • 深浅拷贝 深拷贝
      • 两者对比
    • vue
      • vue2
        • vm.attrs与$listeners
        • vue 和 react 的 diff 算法比较
        • vue 源码分析
        • vue 优化的 diff 策略
        • extends
        • 核心原理篇
        • keep-alive
        • vue 源码分析之 mixins
        • vue 源码分析之 nextTick
        • vue之slot
        • vnode
        • vue 源码分析之 watch
        • 原理
        • vue 源码分析之transition
        • vue 源码分析之异步组件
        • 调用的是 watch
        • 安装
        • react源码分析之portals
        • event 的实现原理(事件的实现原理)
        • 什么是h
        • 分析provide 和 inject
        • vue 源码分析之 use
        • v-model
        • vue源码分析之vuex
        • 响应式原理
        • 初始化的流程
        • 组件更新
        • 编译
        • 父子组件生命周期
        • 原理
        • 多实例
        • Vue 面试
        • 源码研读一
        • 响应式原理
        • 常见问题
        • 数组的劫持
        • vue之自定义指令
        • 运行机制全局概览
      • vue3相比vue2的提升点
        • vue composition api
        • vue3的虚拟dom优化
        • vue3层面的双向数据绑定
        • 预处理优化
  • 重构
    • notification
      • 讲解
  • 面试
    • AfterShip经历
      • JS对URL进行编码和解码
      • ShippingLabelTemplate
      • 接入keycloak详解
      • reCAPTCHA接入
      • yalc与动态解决升级的依赖包
      • RBAC 简介
      • 多语言计划
      • 接入Google登录及其主动弹出快捷登录方式
      • 读书计划
        • 传染
        • 这就是OKR
    • 编程猫经历
      • 2024.1.16
      • 2025.2.20
      • 2025.2.21
      • 2025.2.26
      • 2025.3.28
      • 2025.3.3
      • 2025.3.7
      • 行动轨迹
      • 面试主观题
    • 腾讯经历
      • 2022.02.21
      • 2022.03.30
      • 2022.04.24
      • 2022.04.25
      • 2022.04.27
      • 2022.04.28
      • 2022.04.29
      • 2022.05.05
      • 不同公司的面试关注点不同
      • 2022.05.07
      • 2022.05.09
      • 2022.05.10
      • 2022.05.11
      • 2022.05.12
      • 2022.05.13
      • 2022.05.16
      • 2022.05.17
      • 2022.05.19
      • 2022.05.27
      • 面试
      • 行动轨迹
      • 面试主观题
    • 针对字节
      • 2022.05.14
      • 2022.05.17
      • HR面试准备
      • Promise的相关题目
      • React 进阶实践指南(二)
      • React 面试准备
      • vue 与 react 有什么不同 (react 和 vue 有什么区别)
      • TypeScript 全面进阶指南
      • cookie和session区别
      • express 面试准备 koa 中间件原理
      • next面试准备
      • requestCallBack
      • interface 与 type 异同点
      • 取消 promise
      • 如何设计一个前端项目
      • 进阶篇
      • 早早聊面试准备
      • 自动化部署
      • 挖掘项目的深度
      • 面试
      • 出题指数
    • 魅族经历
      • 2020.09.11
      • 一灯
      • 一灯
      • 一灯
      • 2020.09.20
      • 2020.09.21
      • 网易二面
      • 2020.09.23
      • 头条
      • 360 金融面试题
      • 富途一面
      • 算法
      • 字节
      • 2020.11.04
      • baidu 一面
      • meta 标签的作用
      • 字节
      • 2020.11.22
      • 2020.11.25
      • 微前端接入笔记
      • 面试的基本原则
由 GitBook 提供支持
在本页
  • 函数声明 和 函数表达式
  • 对象方法、 类方法、 原型方法
  • 原型链
  • prototype 和 proto
  • constructor 和 prototype
  • 继承 多态 封装
  • 子类继承父类( js 的 6 种继承方式)
  • 继承方式
  • ES5 方式的实现方式(最佳实践)
  • 多态
  • 封装

这有帮助吗?

  1. JS

ES5 之原型(一)

本文会分为上下两篇。上篇会讲 ES5 相关的东西, 下篇会讲 ES6 相关的东西。

函数声明 和 函数表达式

既然是讲类,那不得不从函数开始讲起,以及涉及到到的原型链相关的东西。我们写第一个 hellowworld 时的场景不知道你们还记不记得。

function HelloWorld() {
  console.log("hello world");
}
var HelloWorld = function () {
  console.log("hello world");
};

函数声明 与 函数表达式 的最主要区别就是函数名称, 在函数表达式中能够忽略它,从而创建匿名函数,一个匿名函数可以被用作一个 IIFE(即时调用的函数表达式), 一旦它定义就运行。 注意点: 函数表达式没有提升,不像函数声明, 你在定义函数表达式之前不能使用函数表达式。

obj();

const obj = function () {
  console.log("obj");
};

//ReferenceError: Cannot access 'obj' before initialization

如果想再函数体内部引用当前函数, 则需要创建一个命名函数表达式。 然后函数名称将会作为函数体(作用于)的本地变量。

var math = {
  factorial: function factorial(n) {
    if (n <= 1) {
      return 1;
    }
    return n * factorial(n - 1);
  },
};
const obj = math.factorial(3);
console.log("obj: ", obj); //6

被函数表达式赋值的那个变量会有一个 name 属性, 如果我们直接调用这个跟函数名有区别吗?

对象方法、 类方法、 原型方法

function People(name) {
  this.name = name;
  //对象方法
  this.Introduce = function () {
    alert("My name is " + this.name);
  };
}
//类方法
People.Run = function () {
  alert("I can run");
};
//原型方法
People.prototype.IntroduceChinese = function () {
  alert("我的名字是" + this.name);
};

//测试

var p1 = new People("Windking");

p1.Introduce(); //对象方法需要通过实例化对象去调用

People.Run(); //类方法不需要通过实例化对象去调用

p1.IntroduceChinese(); //原型方法也需要通过实例化对象去调用
  1. 对象方法包括构造函数中的方法以及构造函数原型上面的方法。

  2. 类方法,相当于函数,可以为其添加函数属性及方法。

  3. 原型方法一般用于对象实例共享,比如

Person.prototype.sayName = function () {
  console.log(this.name);
};

在原型上面添加该方法,就能实现共享。这样就不用每一次初始化一个实例的时候,为其分配相应的内存了。

原型链

每一个切图仔最开始接触前端,最头疼的就是原型与原型链相关的东西。那么我们先来梳理下。

原型是什么?

在 JavaScript 中原型是一个 prototype 对象,用于表示类型之间的关系。

原型链是什么?

JavaScript 万物都是对象,对象和对象之间也有关系,并不是孤立存在的。对象之间的继承关系,在 JavaScript 中是通过 prototype 对象指向父类对象,直到指向 Object 对象为止,这样就形成了一个原型指向的链条,专业术语称之为原型链。

在我们学习之前先来看几个问题理清几个概念

prototype 和 proto

其实在 JavaScript 代码还没有运行的时候,JavaScript 环境已经有一个 window 对象 window 对象有一个 Object 属性, window.Object 是一个函数对象. window.Object 这个函数对象有一个重要的属性是 prototype.

var obj = {};
obj.toString();
console.log("obj", obj);

obj 变量指向一个空对象,这个空对象有个 _proto 属性指向 window.Object.prototype 调用 obj.toString()的时候,obj 本身没有 toString,就去 obj.proto 上面去找 toString 所以你调用 obj.toString 的时候,实际上是调用 window.Object.prototype.toString

我们在看这个例子:

var arr = [];
arr.push(1);
console.log(arr);

arr.proto 指向 window.Array.prototype。 这样当你在调用 arr.push 的时候,arr 自身没有 push 属性,就上去 arr.proto 上找。最终找到 push 方法。如果是 arr.valueOf 呢,arr 自身没有,Array.prototype 也没有, 那么他会去 arr.proto.proto 就是 window.Object.prototype, 所以 arr.valueOf 其实就是 window.Object.valueOf

所以我们可以得出如下概念:

prototype 是构造函数的属性,构造函数也是对象。 而 proto 是对象的属性, 函数的 prototype 是个一对象, 对象的 proto 属性指向原型, proto 将对象和原型连接起来组成了原型链。

  • Object 是所有对象的爸爸, 所有对象都可 proto 指向

  • Function 是所有函数的爸爸, 所有函数都可以通过 proto 找到它

他们的区别:

  • prototype 是让你知道用什么属性

  • proto 是让你知道都有什么属性

constructor 和 prototype

原型(prototype)是构造函数的一个属性,是一个对象。constructor 是绑在实例上面的,不是绑在原型链上面的。,constructor 则代表实例拥有的方法。可以浅显的认为 prototype 向下指, constructor 向上指, 这里的向上指代表的是往父类或者原型上面。

var obj = new Object();
console.log(
  "Object.prototype.constructor == Objcect && Objcect === obj.constructor",
  Object.prototype.constructor == Object && Object == obj.constructor
);
//这个答案是什么

在前面说过,prototype 是让你知道用什么属性,Object.prototype 指的是 Object 类原型的 constructor 方法。

function Bottle() {
  this.name = "a";
  this.sayHello = function () {
    console.log("this.name", this.name);
  };
}
Bottle.prototype.sayIntroduce = function () {
  console.log("this.sayIntroduce", this.name);
};
var bot1 = new Bottle();
var bot2 = new Bottle();
console.log(Bottle.prototype.constructor == Bottle); //ture
console.log(bot1);

构造函数实例出来的对象,可以得到构造函数对象中的属性,方法。等等还有一个什么 proto。我们仔细点进去,有两个东西 constructor: Bottle()。这是因为我们是由 Bottle,new 出来。我们在继续点下去,还有_proto: 的 constructor: Object()。


var obj = new Object();
obj.__proto = 六个属性:constructor, hasOwnProperty, toLocaleString

obj.constructor指向Objector
function Person(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
}

Person.prototype.sayHello = function () {
  console.log(this.name);
};

这是我们构造函数的方法, 我们添加下面东西 看能输出什么

var obj1 = new Person("red", 10, "man");
var obj2 = new Person("yellow", 11, "male");
console.log("obj1.sayHello === obj2.sayHello", obj1.sayHello === obj2.sayHello);

通过构造函数生成的实例对象时,会自动为实例对象分配原型对象。每个构造函数都有一个 prototype 属性,这个属性就是实例对象的原型对象。

原型对象上的所有属性和方法, 都能被派生对象共享。

function Person(name) {}

Person.prototype = {
  constructor: Person,
  sayHello: function () {},
};

注意:需注意的是在上面的代码中,我们将 Person.prototype 设置为一个新创建的对象。会导致 Person.prototype 对象原来的 constructor 属性不再指向 Person, 这里可以像上面那样,特意的把 constructor 设置为 Person 。

var arr = [];
arr.name = "ar1";
Array.prototype.name = "Ar1";
console.log(arr.name); // ar1

当构造函数自定义的属性名与该构造函数下原型属性名相同时,构造函数的自定义属性优先于原型属性(可以把构造函数理解为内联样式), 而原型属性或者原型方法可以看做是 class)

所以在这里简单总结下构造函数、原型、隐式原型和实例的关系:每个构造函数都有一个原型属性(prototype),该属性指向构造函数的原型对象;而实例对象有一个隐式原型属性(proto),其指向构造函数的原型对象(obj.proto==Object.prototype);同时实例对象的原型对象中有一个 constructor 属性,其指向构造函数。

由于 prototype 是通过函数名,指到其他内存空间独立的函数体,因此没法取得闭包的作用域变量。

Person.prototype.myAge = function () {
  console.log(age);
};
var p1 = new Person(20); // 新建对象p1
p1.myAge(); // 报错 age is not defined

继承 多态 封装

//todo

子类继承父类( js 的 6 种继承方式)

1.直接继承 prototype

function Person(name) {
  this.name = name;
  // this.sayHello = function() {
  //     console.log('hello', this.name)
  // }
}

Person.prototype.sayHello = function () {
  console.log("hello", this.name);
};

function Student(name, grade, sex) {
  this.name = name;
  this.grade = grade;
  this.sex = sex;
}

Student.prototype = Person.prototype;
Student.prototype.constructor = Student;
var std1 = new Student("b", 11);
std1.sayHello(); // 实例和原型 均访问的到。
console.log("std1.prototype: ", Student.prototype); // sayHello
console.log(Person.prototype.constructor); //Student

缺点是 Student.prototype 和 Person.prototype 现在都指向同一个对象了,那么任何对 Student.prototype 修改, 都会映射到 Person.prototype 上。

  1. 借用构造函数继承

call/apply 将 子类 的 this 传给 父类 , 再将 父类的属性绑定到 子类 的 this 上。

function Person(name, grade) {
  this.name = name; //实例属性
  this.grade = grade; // 实例属性
  this.sayHello = function () {
    //实例方法
    console.log("hello", this.name, this.grade);
  };
}

function Student(name, grade, sex) {
  Person.apply(this, arguments);
  // 或者 Person.call(this, name, grade)
  //父类 没有构造的属性 无法直接用
  this.sex = sex;
  this.sayIntroduce = function () {
    console.log("sayIntroduce", this.name, this.grade, this.sex);
  };
}
var std1 = new Student("b", 11, "man");
var Per1 = new Person("a", 23, "man");
std1.sayIntroduce();
std1.sayHello();

console.log("Per1.name", Per1.sex); //拿不到子类的东西, 不然就是双向绑定了
Per1.sayIntroduce(); //拿不到子类的东西, 不然就是双向绑定了

console.log("std1.name: ", std1.name);
console.log("std1.grade: ", std1.grade);
console.log("std1.sex: ", std1.sex);

console.log(" student instanceof Student", std1 instanceof Student);
console.log(" student instanceof Student", std1 instanceof Person);

instanceof 运算符可以用来判断某个构造函数的 prototype 属性是否存在另外一个要检测对象的原型链上.

这种在构造函数内部借用函数而不借助原型继承的方式被称之为 借用构造函数式继承. 但是这样做的缺点就是没有继承 Person 的原型方法和属性。

//我们将上面 Person 类里面的 this.sayHello 注释掉
//补上
Person.prototype.sayHello = function () {
  console.log("hello", this.name);
};

std1.sayHello();

ES5.JS:103 Uncaught TypeError: std1.sayHello is not a function

  1. 组合寄生式

先看下面代码

function Person(name) {
  this.name = name;
  // this.sayHello = function() {
  //     console.log('hello', this.name)
  // }
}

Person.prototype.sayHello = function () {
  console.log("hello", this.name);
};

function Student(name, grade, sex) {
  Person.call(this);
  this.name = name;
  this.grade = grade;
  this.sex = sex;
}

Student.prototype = new Person(); //通过改变原型对象实现继承
Student.prototype.constructor = Student; // 保持构造函数和原型对象的完整性
var std1 = new Student("b", 11);
var std2 = new Student("a", 22);
std1.sayHello(); // 实例和原型 均访问的到。
console.log(std1.hasOwnProperty("name")); // 为 false 说明是继承来的属性, 为 true 代表是自身的属性
console.log(std1.sayHello === std2.sayHello); // true,复用了方法
console.log("std1.prototype: ", Student.prototype);

4.空对象

var Obj = function () {};
Obj.prototype = Person.prototype;
Student.prototype = Obj.prototype;
Student.prototype = new Obj();
Student.prototype.constructor = Student;

以上继承方式或多或少都有点缺点,那么我们有没有完美的解决方案呢

5.最佳组合方式

function Animal(name) {
  this.name = name;
}
Animal.prototype.species = "animal";

function Leo(name) {
  Animal.call(this, name); // 1、
}
Leo.prototype = new Animal(); // 2
Leo.prototype.constructor = Leo; // 3、

//既然不能直接在两者之间画等号, 就造一个过渡纽带呗. 能够关联起原型链的不只有 new,  Object.create() 也是可以的.

function Animal(name) {
  this.name = name;
}
Animal.prototype.species = "animal";

function Leo(name) {
  Animal.call(this, name);
}
Leo.prototype = Object.create(Animal.prototype);
Leo.prototype.constructor = Leo;

// 这种在构造函数内部借函数同时又间接借助原型继承的方式被称之为寄生组合式继承.

继承方式

① 原型链继承

  • 每一个构造函数都有一个原型对象

  • 原型对象又包含一个指向构造函数的指针

  • 而实例则包含一个原型对象的指针


但是第一种方式的缺点是,子类的实例共享了父类的引用属性,这样当一个子类的实例修改了这个引用属性,就会影响到另一个子类的实例。

② 构造函数继承(借助 call)

盗用构造函数的另一个优点就是可以给父构造函数传递参数。

function Child3(name) {
  // 第二次调用
  Parent.call(this, name);
  this.type = "child3";
}
// new的时候会调用
Child3.prototype = new Parent();

它的优点: 使父类的引用属性不会被共享 它的缺点是 只能继承父类的实例、属性和方法,不能继承原型属性和方法

③ 组合继承

function Child3(name) {
  // 第二次调用
  Parent.call(this, name);
  this.type = "child3";
}
// new的时候会调用
Child3.prototype = new Parent();
Child3.prototype.constructor = Child3;

④ 原型式继承

  • 是用作新对象原型的对象

  • 是为新对象定义额外属性的对象(可选参数)

⑤ 寄生式继承

使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力在进行增强,添加一些方法。

Objcect.create(p);

寄生式继承相比于原型式继承还是在父类基础上添加了更多的方法

⑥ 寄生组合式继承

function clone(parent, child) {
  // 采用Object.create() 可以减少组合继承中多进行一次构造的过程
  child.prototype = Object.create(parent.prototype);
  child.prototype.constructor = child;
}

ES5 方式的实现方式(最佳实践)

最佳实践其实就是在组合继承的基础上修改原型继承的方式,封装 inheritPrototype 函数,专门处理子类继承父类的原型逻辑.inheritPrototype 函数。

function Person(name) {
    this.name = name;
}
Person.prototype.sayHello = function() {
    console.log(‘hi’ + this.name)
}

function Student(name, grade) {
    Person.call(this, name) // tips
    this.grade = grade;

}

inheritPrototype(Student, Person);

Student.prototype.selfIntroduce = function() {
    console.log('my ' + this.name + ' grade ' + this.grade)
}

function inheritPrototype(Child, Parent) {
    // 继承原型上的属性
    // Sub.prototype = new Super()  把这行代码替换成下面一行
    Child.prototype  = Object.create(Parent.prototype); //tips

    // 修复 constructor
    Child.prototype.constructor = Child;  //tips

}

// 这里只调用了一次Super构造函数,而且避免了Sub.prototype上不必要也用到的属性,因此此方式效率更高。可以说是继承的最佳模式。

var student = new Student('obj', 23)
console.log(student.name); //‘obj’
student.sayHello(); // obj
student.sayHello(); //23
student.hasOwnProperty(‘name’); //true

多态

JavaScript 的多态,我们先看百度百科的介绍:多态(Polymorphism)按字面的意思就是“多种状态”。 在面向对象语言中,接口的多种不同的实现方式即为多态。 多态的优点

  1. 扩展性强

  2. 消除类型之间的耦合关系

  3. 接口性

  4. 可替换行

存在的三个必要条件

  • 继承

  • 重写

  • 父类引用指向子类对象

function Person() {
  this.say = function (vocation) {
    console.log("My vocation is", vocation.name);
    console.log("My vocation is", vocation.constructor);
  };
}

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

function Teacher(name) {
  this.name = name;
}
var student = new Student("student");
var teacher = new Teacher("teacher");
var person = new Person();
person.say(student);
person.say(teacher);

封装

function Person(name, age) {
  this.name = name;
  var age = age; // 在实例中无法被调用
}
var p1 = new Person("obj", 20);
console.log(p1); // Person ->{name: "obj"}  无法访问到age属性,这就叫被封(装)起来了。
function Person(age) {
  var age = age; // 私有变量
  this.showAge = function () {
    // 特权方法
    console.log(age);
  };
}
var p1 = new Person(20); // 新建对象p1
p1.showAge();
上一页迭代器下一页ES6之类(二)

最后更新于2个月前

这有帮助吗?

obj.prototype
arry._proto
构造函数
原型链