10TypeScript泛型进阶

一. 写在前面

  • 本次内容全部是js的,没有ts内容,学习这些内容是为了之后更好的学习ts,如果不学习这些内容可能会导致之后ts的学习有地方难以理解

二. 面向对象之原型

  • 假设我需要用js造一个小兵,以下是基础代码
js 复制代码
// JavaScript 造一个小兵 
const 近战兵 = {
  兵种:"近战",
  血量:1488,
  物理攻击力:60,
  护甲:180,
  金钱:42,
  补刀奖励:16,
  出生:function (){/*出生动画*/},
  死亡:function (){/*死亡动画*/},
  攻击:function (){/*攻击特效*/ },
  行走:function (){/*行走动画*/ },
}
兵营(近战兵)
  • 需求: 造100个
    • 有两种方式,第一种写100遍,这种方式明显很不合理
js 复制代码
const 近战兵1={/*略*/}
const 近战兵2={/*略*/}
/* ... */
const 近战兵100={/*略*/}
兵营(近战兵1, 近战兵2, ···, 近战兵100)
  • 第二种方式,使用循环,但是这种方式还有问题,太浪费内存了
js 复制代码
const list = [] 
for (let i=0; i<100; i++) {
  list.push({
    id: i,
    兵种: '近战',
    血量: 1488,
    物理攻击力: 60,
    护甲: 180,
    金钱: 42,
    补刀奖励: 16,
    出生: function(){/*出生动画*/},
    死亡: function(){/*死亡动画*/},
    攻击: function(){/*攻击特效*/},
    行走: function(){/行走动画*/},
  })
}
兵营(...list)
  • 我们通过分析,可以发现,金钱,补刀奖励,以及四个函数是一样的,共用的,我们期望实现以下效果
js 复制代码
{
  id: 1,
  血量: 1488,
  物理攻击力: 60,
  护甲: 180,
  more: 近战兵公有属性
}
{
  id: 2,
  血量: 1488,
  物理攻击力: 60,
  护甲: 180,
  more: 近战兵公有属性
} 
近战兵公有属性 = {
  兵种: '近战',
  金钱: 42,
  补刀奖励: 16,
  出生: function(){/*出生动画*/},
  死亡: function(){/*死亡动画*/},
  攻击: function(){/*攻击特效*/},
  行走: function(){/*行走动画*/},
}
  • 我们通过下图来体会一下这样做的好处,从1100个属性降到507个属性,大大减少了内存,我们的核心思想就是把所有的属性分成独有属性和共有属性
  • 经过上面这个步骤之后,我们可以得到造100个士兵,同时内存尽量少的方法
js 复制代码
const 近战兵公有属性 = {
  兵种: '近战',
  金钱: 42,
  补刀奖励: 16,
  出生: function(){/*出生动画*/},
  死亡: function(){/*死亡动画*/},
  攻击: function(){/*攻击特效*/},
  行走: function(){/*行走动画*/},
}
for (let i=0; i<100; i++) {
  const 近战兵 = {
    id: i,
    血量: 1488,
    物理攻击力: 60,
    护甲: 180,
  }
  近战兵.__proto__ = 近战兵公有属性 // 隐藏属性指向近战兵公有属性
  list.push(近战兵)
}
兵营(...list)

三. 再次去优化代码

  • 目前代码还存在的问题: 代码过于分散,分成了两部分代码去创建士兵,我们可以这样去优化
js 复制代码
// soldier.js
export const 创建近战兵 = function(id) {
  const 近战兵 = {
    id: i,
    血量: 1488,
    物理攻击力: 60,
    护甲: 180,
  }
  近战兵.__proto__ = 近战兵公有属性
  return 近战兵
}
const 近战兵公有属性 = {
  兵种: '近战',
  金钱: 42,
  补刀奖励: 16,
  出生: function(){/*出生动画*/},
  死亡: function(){/*死亡动画*/},
  攻击: function(){/*攻击特效*/},
  行走: function(){/*行走动画*/},
}
// main.js
import {创建近战兵} from './soldier'
const list = []
for (let i=0; i<100; i++) {
  list.push(创建近战兵(i))
}
兵营(...list)
  • 目前还存在的缺点,创建近战兵和近战兵公有属性不够内聚,这两段代码非常互相依赖,但是仅仅靠近战兵.__proto__ = 近战兵公有属性去关联
    • 这里解释一下什么是高内聚低耦合,所谓的高内聚就是该在一起的代码不要分开,低耦合就是能分开的代码尽量不要放在一起
js 复制代码
// 进一步优化代码,让代码内聚,这样子之后,这两段代码彼此无法分开 
export const 创建近战兵 = function(id) {
  const 近战兵 = {
    id: i,
    血量: 1488,
    物理攻击力: 60,
    护甲: 180,
  }
  近战兵.__proto__ = 创建近战兵.prototype
  return 近战兵
}
const 创建近战兵.prototype = {
  constructor: 创建近战兵
  兵种: '近战',
  金钱: 42,
  补刀奖励: 16,
  出生: function(){/*出生动画*/},
  死亡: function(){/*死亡动画*/},
  攻击: function(){/*攻击特效*/},
  行走: function(){/*行走动画*/},
}
  • 以上这段代码非常经典,于是js之父决定把这段代码推广给每一个写js的程序员
js 复制代码
// 进一步优化代码,让代码内聚,这样子之后,这两段代码彼此无法分开 
export const 创建近战兵 = function(id) {
  const 近战兵 = {
    id: i,
    血量: 1488,
    物理攻击力: 60,
    护甲: 180,
  } // const 近战兵,这句不需要写,肯定会创建一个对象,但是要把独有属性写清楚
  近战兵.__proto__ = 创建近战兵.prototype // 这句不需要写,每个对象都肯定要加
  return 近战兵 // 不需要写,每一个构造函数肯定要return一个对象
}
const 创建近战兵.prototype = { // 这句固定下来就叫prototype
  constructor: 创建近战兵 // 这句不需要写,自动加一个constructor,指向上面函数,这样就做到了高内聚
  兵种: '近战',
  金钱: 42,
  补刀奖励: 16,
  出生: function(){/*出生动画*/},
  死亡: function(){/*死亡动画*/},
  攻击: function(){/*攻击特效*/},
  行走: function(){/*行走动画*/},
}
  • 其实剩下的代码只剩下独有属性和共有属性
js 复制代码
// soldier.js
export const 近战兵 = function(id) {
  this.id = id
  this.血量 = 1488
  this.物理攻击力 = 60
  this.护甲 = 180
}
近战兵.prototype.兵种 = '近战'
近战兵.prototype.金钱 = 42
近战兵.prototype.补刀奖励 = 16
近战兵.prototype.出生 = function(){/*出生动画*/}
近战兵.prototype.死亡 = function(){/*死亡动画*/}
近战兵.prototype.攻击 = function(){/*攻击特效*/}
近战兵.prototype.行走 = function(){/*行走动画*/}

// main.js
import {近战兵} from './soldier'
const list = []
for (let i=0; i<100; i++) {
  list.push(new 近战兵(i)) // 注意这里的new
}
  • 这段代码还能优化吗?通过浅拷贝实现优化
js 复制代码
// soldier.js
export const 近战兵 = function(id) {
  copy(this, { id: id, 血量: 1488, 物理攻击力: 60, 护甲: 180})
}

近战兵.prototype = {
  constructor: 近战兵, // 这样写会把constructor覆盖,因此这里需要加回来
  兵种: '近战'
  金钱 : 42
  补刀奖励: 16
  出生 : function(){/*出生动画*/}
  死亡 : function(){/*死亡动画*/}
  攻击 : function(){/*攻击特效*/}
  行走 : function(){/*行走动画*/}
}

四. new做了哪些事

  • 创建一个完全空的对象,放到this上面 this = {}
  • 把这个对象的隐藏属性指向公有属性 this.__proto__ = 近战兵.prototypes
  • 返回this,return this
  • 近战兵.protoType 这件事情,包括里面的constructor: 近战兵都是js做的,并非new去做的
    • 当在浏览器里面声明一个非箭头函数然后通过console.dir,即可发现自带prototype和constructor

五. 总结: JS如何创建对象

  1. 以new为语法糖
js 复制代码
var a = {} // 这只是简写
var a = new Object() // 实际上是这样写
// 同理
var array = []
var array = new Array()
  1. 用构造函数给对象添加独有尾性
  2. 用构造函数的prototype容纳共有隐性
  3. 使用属性查找规则 1. 读取obj的'x'属性时,先看obj的独有属性有没有x 2. 再看obj的共有属性有没有x 3. 再去看obj的共有属性的共有属性有没有x 4. 直到共有属性为null,则认为obj.x不存在

六. js如何知道共有属性有哪些

  • 以前用的是obj.__proto__这种形式
  • 现在用obj.[[prototype]]

七. 一些面试题

  • 什么原型?
    • 第一题
js 复制代码
const obj = new 近战兵(i)
// 请问obj的原型是,答案: 1 和 2,他们是相等的
// 1. 近战兵.prototype
// 2. obj.__proto__ 或者 obj.[[prototype]]
  • 第二题
js 复制代码
const fn = function() {}
// 函数比较特殊会有一个prototype属性
// 请问fn的原型是? 2 和 3,这里的关键点是要把fn当作一个obj,1是把fn当作函数去解释,每一个非箭头函数都有一个prototype
// 1. fn.prototype
// 2. fn.__proto__
// 3. Function.prototype
  • 第三题
js 复制代码
// Function的原型是什么? 
// Function.prototype不是它的原型,是这个函数构造出来的对象的原型
Function.__proto__ === Function.prototype // true,函数的原型是其共有的属性
// Function的构造函数是什么? 是它本身
Function.prototype.constructor === Function // true
// 1. 浏览器构造了Function
// 2. 浏览器卸了代码 Function.constructor = Function
  • 总结: 原型就是指一个对象的共有属性所在地

八. 画出js世界

九. 相关文章

相关推荐
zqx_720 分钟前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己36 分钟前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色1 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2342 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
BigYe程普2 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H2 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍2 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai2 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端