JS 对象分类

297 阅读3分钟
  • 对象需要分类吗?
  • 这是一个值得思考的问题
  • 我们来做一个小程序。

输出各种形状的面积和周长

正方形

代码

let square = {
  width: 5,    //边长
  getArea(){   //面积
    return this.width * this.width 
  },           //this看成当前对象
  getLength(){ //周长
    return this.width * 4
  }
}

分析

  • 正方形拥有三个属性:边长、面积、周长

给我来一打正方形

  • 这样写代码的,要么是新人,要么是傻子
let square = {
  width: 5,
  getArea(){ 
    return this.width * this.width 
  },
  getLength(){
    return this.width * 4
  }
}
let square2 = {
  width: 6,
  getArea(){ 
    return this.width * this.width 
  },
  getLength(){
    return this.width * 4
  }
}
let square3 = { ...
  • 简单,用循环搞定 for 循环
  • 不就是12个对象嘛

代码

let squareList = []
for(let i = 0; i<12; i++){   //0取,12不取
  squareList[i] = {
    width: 5,
    getArea(){ 
      return this.width * this.width 
    },
    getLength(){
      return this.width * 4
    }
  }
}
  • 看,搞定了

  • width 不全是 5 啊,怎么办

  • 好办

代码

let squareList = []
let widthList = [5,6,5,6,5,6,5,6,5,6,5,6]
for(let i = 0; i<12; i++){
  squareList[i] = {
    width: widthList[i],
    getArea(){ 
      return this.width * this.width 
    },
    getLength(){
      return this.width * 4
    }
  }
}
  • 看,又搞定了
  • 垃圾代码,浪费了太多内存,自己画内存图就知道了
  • 赶紧动手给我画内存图!
  • 你画了之后,再看我怎么画
  • 会画内存图的人,比其他人理解得更好

画内存图发现问题:重复的函数

1.png

  • 所以你看,你的代码很垃圾(新手就是写bug,代码都是bug。代码储备不足以写出没有bug的代码)
  • 继续改

借助原型

  • 将12个对象的共用属性放到原型里

代码

let squareList = []
let widthList = [5,6,5,6,5,6,5,6,5,6,5,6]
let squarePrototype = {               //建立square原型
  getArea(){ 
    return this.width * this.width    //放共有函数1获取面积
  },
  getLength(){                        //放共有函数2获取边长
    return this.width * 4
  }
}
for(let i = 0; i<12; i++){            //创建这个对象
  squareList[i] = Object.create(squarePrototype)    //这个对象的原型是上面的square原型
  squareList[i].width = widthList[i]
}

1.jpg

1.jpg

  • 看,双搞定了
  • 还是垃圾代码!你创建 square 的代码太分散了!
  • 太分散了吗
  • 那就把代码抽离到一个函数里,然后调用函数

抽离到函数

let squareList = []
let widthList = [5,6,5,6,5,6,5,6,5,6,5,6]
function createSquare(width){               //此函数叫做构造函数(可以构造出对象的函数)
  let obj = Object.create(squarePrototype)  // 以 squarePrototype 为原型创建空对象
  obj.width = width                         //添加宽度
  return obj                                //返回这个对象
}                        //把三行代码写到一个函数里。这样把细节写到函数里,直接调用函数并传个宽度就搞定了,叫封装。
let squarePrototype = {
  getArea(){ 
    return this.width * this.width 
  },
  getLength(){
    return this.width * 4
  }
}
for(let i = 0; i<12; i++){
  squareList[i] = createSquare(widthList[i])
  // 这下创建 square 很简单了吧!
  }
  • squarePrototype 原型 和 createSquare 函数 还是分散的
  • 能不能组合在一起

函数和原型结合

let squareList = []
let widthList = [5,6,5,6,5,6,5,6,5,6,5,6]

function createSquare(width){           //声明一个creatSquare可以创建一个Square对象
  let obj = Object.create(createSquare.squarePrototype) // 先使用sP后定义sP吗?时机问题。sP这一行代码不是现在执行。在调用createSquare后才会执行。也就是说代码先写到这里没有执行。cS在倒数第二行才执行。
  obj.width = width
  return obj
}
createSquare.squarePrototype = {      //把原型放到构造函数上,方便通过函数找到原型。
  getArea(){ 
    return this.width * this.width 
  },
  getLength(){
    return this.width * 4 
  },
  constructor: createSquare   //除了把原型放到构造函数上,还把构造函数放到原型上。互相引用,非常紧密抱在一起。方便通过原型找到构造函数
}
for(let i = 0; i<12; i++){
  squareList[i] = createSquare(widthList[i])
  console.log(squareList[i].constructor) 
  // 通过把对象的constructor属性打印出来可以知道谁构造了这个对象:你妈是谁?
  • 这段代码几乎完美,没有优化余地
  • js之父就想这段代码这么好还省内存为什么不固定到js基因里,让每个 JS 开发者直接用呢
  • 把new 操作符用来简化刚才的逻辑
  • 让我们感受 JS 之父的爱

1.jpg

  • 所有js函数从出生的时候自带prototype属性,这个属性里面从出生的时候就有constructor,这个constructor从出生的时候它的值就是函数自身。
  • 没有道理,js之父为了让new可以运行提前规定

函数和原型结合(重写)

let squareList = []
let widthList = [5,6,5,6,5,6,5,6,5,6,5,6]
function Square(width){  //约定俗成
  this.width = width
}
Square.prototype.getArea = function(){ 
  return this.width * this.width 
}
Square.prototype.getLength = function(){
  return this.width * 4
}
for(let i = 0; i<12; i++){
  squareList[i] = new Square(widthList[i])
  console.log(squareList[i].constructor)
}
// 多美,几乎没有一句多余的废话
// 每个函数都有 prototype 属性,这是 JS 之父故意的
// 每个 prototype 都有 constructor 属性,也是故意的

对比

function createSquare(width){
  let obj = Object.create(createSquare.squarePrototype)
  obj.width = width
  return obj
}
createSquare.squarePrototype = {
  getArea(){ 
    return this.width * this.width 
  },
  getLength(){
    return this.width * 4
  },
  constructor: createSquare
}
let square = createSquare(5)

上面的代码被简化为下面的代码 唯一的区别是要用 new 来调用

function Square(width){      //名字不同,直接大写
  this.width = width         //之前要自己创建一个对象,把对象的原型指向拥有getArea和getLength的原型。现在不用写了,new会帮我们写。用this代表新的对象。return不用写,会帮你写。三行被压缩成一行,只要写new。
}
Square.prototype.getArea = function(){   //此处共有属性要一个一个加,不能直接声明prototype等于...。因为prototype已经有了一个属性constructor了,如果贸然声明,constructor就会被搞丢。
  return this.width * this.width 
}
Square.prototype.getLength = function(){
  return this.width * 4
}
let square = new Square(5)   //声明对象用new

总结

new X() 自动做了四件事情

  • 自动创建空对象
  • 自动为空对象关联原型,原型地址指定为 X.prototype
  • 自动将空对象作为 this 关键字运行构造函数
  • 自动 return this
  • ——这就是 JS 之父的爱

构造函数 X

  • X 函数本身负责给对象本身添加属性
  • X.prototype 对象负责保存对象的共用属性

1.jpg

题外话:代码规范

大小写

  • 所有构造函数(专门用于创建对象的函数)首字母大写
  • 所有被构造出来的对象,首字母小写

词性

  • 构造函数(new 后面的函数,只创建一类对象),使用名词形式 如 new Person()、new Object()

  • 其他函数,都是完成某个动作,一般使用动词开头 如 createSquare(5)、createElement('div')

  • 其他规则以后再说

  • 接下来总结一个重要的公式

  • 也是 JS 里唯一的一个公式

如何确定一个对象的原型

为什么

  • let obj = new Object() 的原型是 Object.prototype
  • let arr = new Array() 的原型是 Array.prototype
  • let square = new Square() 的原型是 Square.prototype
  • let fn = new Function() 的原型是 Function.prototype
  • 因为 new 操作故意这么做的

1.png

结论

  • 你是谁构造的
  • 你的原型就是谁的prototype属性对应的对象

原型公式

对象. _proto_ === 其构造函数.prototype

参考资料

www.zhihu.com/question/56… 《_proto_ 和 prototype 存在的意义是什么》方方

来做几个题看看你理解这个公式了吗

难度1

let x = {}

请问:

  1. x 的原型是什么?Object.prototype
  2. x._proto_ 的值是什么?
  3. 上面两个问题是等价的吗?
  4. 请用内存图画出 x 的所有属性

1.jpg

难度2

let square = new Square(5)

请问:

  1. square 的原型是什么?
  2. square.proto 的值是什么?
  3. 请用内存图画出 x 的所有属性

难度3

请问:

  1. Object.prototype 是哪个函数构造出来的?不知道
  2. Object.prototype 的原型是什么?开天辟地没爸没妈
  3. Object.prototype.proto?null
  4. 请用内存图画出上述内容
  • 构造函数、prototype、new我们已经通过 Square 理解了

Square 最终版(存疑)

function Square(width){ 
  this.width = width
}
Square.prototype.getArea = function(){ 
  return this.width * this.width 
}
Square.prototype.getLength = function(){
  return this.width * 4
}
let square = new Square(5)
square.width
square.getArea()
square.getLength()
  • 正方形搞定了 再给我写个圆呗
  • 甲方爸爸总是会提出新(wu)的(li)需求

圆Circle

代码

function Circle(radius){    //radius半径
  this.radius = radius
}
Circle.prototype.getArea = function(){ 
  return Math.pow(this.radius,2) * Math.PI  //Math.pow幂次方
}
Circle.prototype.getLength = function(){    
  return this.radius * 2 * Math.PI
}
let circle = new Circle(5)
circle.radius
circle.getArea()
circle.getLength()
  • 圆搞定了再给我写个长方形呗
  • 甲方爸爸总是会提出新(wu)的(li)需求

长方形Rectangle

代码

function Rect(width, height){ 
  this.width = width
  this.height = height
}
Rect.prototype.getArea = function(){ 
  return this.width * this.height  
}
Rect.prototype.getLength = function(){
  return (this.width + this.height) * 2
}
let react = new Rect(4,5)
rect.width
rect.height
rect.getArea()
rect.getLength()
  • 对象需要分类吗?再次回到开头的问题

对象需要分类

理由一

  • 有很多对象拥有一样的属性和行为
  • 需要把它们分为同一类
  • 如 square1 和 square2,Rect1和Rect2
  • 这样创建类似对象的时候就很方便

理由二

  • 但是还有很多对象拥有其他的属性和行为
  • 所以就需要不同的分类
  • 比如 Square / Circle / Rect 就是不同的分类
  • Array / Function 也是不同的分类
  • 而 Object 创建出来的对象,是最没有特点的对象let x = {}

类型 V.S. 类

类型

  • 类型是 JS 数据的分类,有 7 种
  • 四基两空一对象

  • 类是针对于对象的分类,有无数种
  • 常见的有 Array、Function、Date、RegExp 等

数组对象Array

  • 其他编程语言数组是一个单独的东西,js里数组是对象
  • 定义一个数组
  • let arr = [1,2,3]
  • let arr = new Array(1,2,3) // 元素为 1,2,3
  • let arr = new Array(3) // 长度为 3

数组对象的自身属性

  • '0' / '1' / '2' / 'length'
  • 注意,属性名没有数字,只有字符串

数组对象的共用属性

  • 'push'推进去 / 'pop'跳出来 / 'shift' 升/ 'unshift'压 / 'join'手拉手连起来/'contact'数组连起来
  • 其实就是英语小课堂啦,用法都在 MDN
  • 后面会有单独课程教这些 API

1.jpg

1.jpg

函数对象

  • 其他编程语言函数不一定是对象,但js里函数是对象

定义一个函数

  • function fn(x,y){return x+y}
  • let fn2 = function fn(x,y){return x+y}
  • let fn = (x,y) => x+y
  • let fn = new Function('x','y', 'return x+y') x是第一个参数,y是第二个参数,return x+y是函数体

函数对象自身属性

'name' / 'length'

函数对象共用属性

'call' / 'apply' / 'bind'

  • 后面会有单独课程介绍函数

JS 终极一问

window 是谁构造的

  • Window
  • 可以通过 constructor 属性看出构造者

搜狗截图20210923133936.jpg

window.Object 是谁构造的

  • window.Function
  • 因为所有函数都是 window.Function 构造的

搜狗截图20210923134340.jpg

window.Function 是谁构造的

  • window.Function
  • 因为所有函数都是 window.Function 构造的
  • 自己构造的自己?并不是这样,这是「上帝」的安排
  • 浏览器js引擎构造了 Function,然后指定它的构造者是自己

搜狗截图20210923134451.jpg

class语法

class与prototype之争

  • 非常遗憾关于prototype代码被某些前端认为是过时的
function Square(width){ 
  this.width = width
}
Square.prototype.getArea = function(){ 
  return this.width * this.width 
}
Square.prototype.getLength = function(){
  return this.width * 4
}

ES 6 引入了新语法——class 统治天下

class Square{
  constructor(width){
    this.width = width
  } 
  getArea(){ 
    return this.width * this.width 
  }
 // getLength: function(){
    return this.width * 4
  } // 这种写法报错
}

class 语法引入了更多概念

  • 一旦有了class必须学会java的那一套理论(比如要有static)
class Square{
  static x = 1
  width = 0
  constructor(width){
    this.width = width
  } 
  getArea(){ 
    return this.width * this.width 
  }
  getLength(){
    return this.width * 4
  }
  get area2(){ // 只读属性
    return this.width * this.width
  }
}

新手建议

  • 这么多新语法怎么学

两种学法

1.花一大块时间把 MDN class 文档全部看完,并记下来
2.看到方方用什么,就学一点,课程学完,就都学会了
  • 推荐第二种学法,因为新语法实在太多了
  • 在实践中学,记得更牢

到底有多少新语法

用 class 重写 Circle

代码

class Circle{
  constructor(radius){
    this.radius = radius
  } 
  getArea(){ 
    return Math.pow(this.radius,2) * Math.PI  
  }
  getLength(){
    return this.radius * 2 * Math.PI
  }
}
let circle = new Circle(5)
circle.radius
circle.getArea()
circle.getLength()

用 class 重写 Rect

代码

class Rect{
  constructor(width, height){ 
    this.width = width
    this.height = height
  }
  getArea(){ 
    return this.width * this.height  
  }
  getLength(){
    return (this.width + this.height) * 2
  }
}
let react = new Rect(4,5)
rect.width
rect.height
rect.getArea()
rect.getLength()

原型好,还是类class好?

  • 两种不同的世界观:js希望使用者可以理解对象与对象的关系,java希望使用者更理解抽象层面的关系

搜狗截图20210923142313.jpg

  • 都是用来给对象分类的

作业

客观题

1.jpg

2.jpg

3.jpg

4.jpg

5.jpg

6.jpg

7.jpg

8.jpg

9.jpg

10.jpg

11.jpg

主观题

请不要使用 class,写一个 Person 构造函数,要求以下代码运行通过:

function Person(你来补全代码){
    你来补全代码
}

let person = new Person('frank', 18)
person.name === 'frank' // true
person.age === 18 // true
person.sayHi() // 打印出「你好,我叫 frank」

let person2 = new Person('jack', 19)
person2.name === 'jack' // true
person2.age === 19 // true
person2.sayHi() // 打印出「你好,我叫 jack」
  • 使用Chrome浏览器 作业1.jpg

请用 class 再实现一次上面的功能

class Person {
    你来补全代码
}

let person = new Person('frank', 18)
person.name === 'frank' // true
person.age === 18 // true
person.sayHi() // 打印出「你好,我叫 frank」

let person2 = new Person('jack', 19)
person2.name === 'jack' // true
person2.age === 19 // true
person2.sayHi() // 打印出「你好,我叫 jack」
  • chrome浏览器无法运行,使用Microsoft Edge浏览器

作业2.jpg