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世界

九. 相关文章

相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax