JS对象分类

2020-10-16
4 min read

推荐文章
你可以不会class,但是一定要学会prototype
JS的new到底是干什么的?
JS中proto和prototype存在的意义是什么?
ES6 所有新特性

1.构造函数就是可以构造出对象的函数
2.封装:把细节写到一个函数里,别人只需要调用函数并且传个参数(宽度)
3.函数也是对象
4.js之父为了让new可以运行提前规定:
所有js里的函数自带prototype属性。
prototype属性自带constructor。
constructor的值等于函数本身。
5.共有属性与原型的关系:包含与被包含。
共有属性这整个对象叫做原型。
对象的地址就是原型的地址,里面的每一个属性叫共有属性。

对象需要分类吗?

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

正方形
分析 正方形有三个属性:边长、面积、周长
let square={
  width:5,
  getArea(){
    return this.width * this.width
  },
  getLength(){
    return this.width * 4
  }
}

来一打正方形

let squareList=[]
for(let i=0;i<12;i++){
  squareList[1]={
   width:5, 
  getArea(){
    return this.width * this.width
  },
  getLength(){
    return this.width * 4
  }
 }
}

width不全是5怎么办?

let squareList=[]
let widthList=[5,6,7,8,5,6,7,5,5,4,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
  }
  }
}

垃圾代码,浪费太多内存。

借助原型,将12个对象的共用属性放到原型里

let squareList=[]
let widthList=[5,6,7,8,9,5,6,7,8,9,5,6]
let squarePrototype={
 getArea(){
    return this.width * this.width
  },
  getLength(){
    return this.width * 4
  }
}
for(let i=0;i<12;i++){
  squareList[i]=Object.create(squarePrototype)
  squareList[i].width= widthList[i]
}
控制台输入:squareList

还是垃圾代码,创建的square的代码太分散了。

那就把代码抽离到一个函数里,然后调用函数。

抽离到函数

let squareList=[]
let widthList=[5,6,7,8,9,5,6,7,8,9,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])
  //封装,把细节写到一个函数里,别人只需要调用函数并且传个宽度。
}

squarePrototype原型和createSquare函数还是分散的,能不能组合在一起?

函数也是一个对象,如果把原型放到函数上是不是更紧凑?

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

//声明createSquare函数,它可以创建square对象
function createSquare(width){
  let obj = Object.create(createSquare.squarePrototype) // 先使用后定义?NO
  这段代码在调用createSquare才会执行
  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])//这里才调用createSquare
  console.log(squareList[i].constructor) 
  // constructor 可以知道谁构造了这个对象:你妈是谁?
}

除了把原型放到构造函数上,还把构造函数放到原型上。

拿到函数后可以方便的找到它的原型,也可以通过拿到原型方便的找到它的函数。

这段代码几乎完美,为什么不固定下来,让每个js开发者直接用呢

new操作符,让我们感受js之父的爱

函数和原型结合(重写)

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(){ //设置共用属性(getArea)
  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 属性,也是故意的

js之父为了让new可以运行提前规定:

所有js里的函数自带prototype属性,prototype属性自带constructor。constructor的值等于函数本身。

例子

function f1(){}

console.dir(f1)

控制台输入:f1.prototype.constructor === f1

输出结果:true

1.之前要自己创建对象并把对象的原型指向那个拥有getArea,getLength的原型。
现在new会帮我们做
2.用this代表新的对象
3.return也不用写,new会帮我们做
4.之前会创建个对象,里面有共有属性。
现在需要一个一个加。因为prototype已经有一个constructor属性了,Square.prototype=新对象就会导致constructor被搞丢。
所以最好方式是在prototype上加属性。也可以用Object.assign加。

总结
1.new X()自动做了四件事情
自动创建空对象
自动为空对象关联原型,原型地址为X.prototype
自动将空对象作为this关键字运行构造函数
自动return this
–这就是JS之父的爱
2.构造函数X
X函数本身负责给对象本身添加属性
X.prototype对象负责保存对象的共用属性
例子

function Dog(name){
  this.name=name
  this.color='白'
  this.king='萨摩耶'
}
Dog.prototype.wangwang=function(){console.log('汪汪')}
Dog.prototype.run=function(){console.log('狗在跑')}
let dog1=new Dog('小白')

控制台输入:dog1
输出结果:Dog {name: '小白', color: '白', king: '萨摩耶'}

控制台输入:dog1.wangwang()
输出结果:汪汪

控制台输入:dog1.run()
输出结果:狗在跑

this指还未创建出来的新对象dog1

代码规范

1.大小写

所有构造函数首字母大写

function add(x,y){return x+y}  //不是构造函数不用大写
function Dog(name){this.name=name}

所有被构造出来的对象,首字母小写

let dog1=new Dog()

2.词性

(1)new 后面的函数,使用名词形式

如new Person()、new Object()

(2)其它函数,一般使用动词开头

普通函数一般都用动词

如createSquare(5)、createElement(‘div’)

function createSquare(width){…}

其它规则以后再说

问题

1.怎么确定要传几个参数?

function Square(width){…}

自己写的话清楚。但如果是别人提供的构造函数,我怎么知道要接受几个参数?

let arr1=new Array(3) 

数组是3还是长度为3的数组?

只能看文档🔍 mdn array

new Array(element0,element1[,…[,elementN]])//多个参数就是里面的元素

new Array(arrayLength) //一个参数就是数组的长度

js唯一的一个公式

如何确定一个对象的原型?

1.为什么

let obj=new Object()的原型是Object.prototype

let arr=new Array()的原型是Array.prototype

let square=new Square()的原型是Square.prototype

let fn=new Function()的原型是Function.prototype

2.因为new操作故意这么做的

结论

new后面是什么,原型就是 对应的.prototype

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

原型公式

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

两个原型地址是一样的!

练习1

let x={}

1.x的原型是什么?x.__proto__的值是什么?上面两个问题是等价的吗?

let x=new Object{}

x.proto=Object.prototype

所以x的原型就是Object.prototype这个对象

x.__proto__存的地址才是其原型

2.用内存图画出x的所有属性

练习2

let square=new Square(5)

1.square的原型是什么?square.__proto__的值是什么?

square.proto=Square.prototype

2.用内存图画出square的所有属性,略

练习3

1.Object.prototype是哪个函数构造出来的?

不知道,它没有爸爸也没有妈妈

2.Object.prototype的原型是?

没有原型

3.Object.prototype.proto?

null

4.用内存图画出上述内容,略

所有「函数对象」的「构造函数」都是 Function

window.Function是一个函数对象,那么这个「函数对象」的构造函数是Function;

window.Object是一个函数对象,那么这个「函数对象」的构造函数是Function;

window.Function 是一个函数对象,那么这个「函数对象」的__proto__是(对象. proto===其构造函数.prototype)

Square最终版(存疑),还可以进一步优化,以后再说。

对象需要分类

用构造函数再写个圆

function Circle(radius){
  this.radius = radius
}
Circle.prototype.getArea=function(){
  return Math.pow(this.radius,2) * Math.PI
}
Circle.prototype.getLength=function(){
  return this.radius * 2 * Math.PI
}
let c1=new Circle(10)//声明一个新的圆
控制台输入:c1.radius
输出结果:10
控制台输入:c1.getArea()
输出结果:314.1592653589793
控制台输入:c1.getLength()
输出结果:62.83185307179586

那再来个长方形

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 r1=new Rect(4,5) //声明一个新的长方形
控制台输入:r1
输出结果:Rect {width: 4, height: 5}
控制台输入:r1.getArea()
输出结果:20
控制台输入:r1.getLength()
输出结果:18

对象需要分类吗?

需要分类

理由一

有很多对象拥有一样的属性和行为,需要把它们分为同一类

如square1和square2,这样创建类似对象的时候就很方便

理由二

但是还有很多对象拥有其他的属性和行为,所以就需要不同的分类

比如Square/circle/Rect就是不同的分类

Array/Function也是不同的分类

而Object创建出来的对象,是最没有特点的对象。

类型 VS 类

类型

类型是JS数据的分类,有7种(4基2空1对象)

类是针对于对象的分类,有无数种

常见的有Array、Function、Date、RegExp正则等

object是最没有特色的类,现在来看下有特色的类

第一种.数组对象

1.定义一个数组

let arr=[1,2,3] //缩写

let arr=new Array(1,2,3) //元素为1,2,3

let arr=new Array(3) //长度为3

关注一个对象时主要看自身属性和共用属性

2.数组对象的自身属性

自身属性有:‘0’/‘1’/‘2’/’length’

注意:属性名没有数字,只有字符串。数组里的都是字符串不是数字!

3.数组对象的共用属性

共用属性有:‘push’/‘pop’/‘shift’/‘unshift’/‘join’

用法都在MDN,后面会单独教这些API

数组对象比普通对象多一层共有属性

第二种.函数对象

1.定义一个函数

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’) //重要

2.函数对象自身属性

’name’/’length’

例子

let fn = new Function('x', 'y', 'return x+y')
console.dir(fn)

3.函数对象共用属性

‘call’/‘bind’/‘apply’

后面会单独介绍函数

JS终极一问

1.window是谁构造的?

Window(大写)

所以 window.proto === Window.prototype

2.window.Object是谁构造的?

window.Function

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

3.window.Function是谁构造的?

window.Function

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

自己构造自己?并不是这样,这是「上帝」的安排

浏览器构造了Function,然后指定它的构造者是自己

window.Function.constructor === window.Function

es6新语法class

某些前端认为prototype是过时的,我的想法相反,但是不管怎样还是要学class。

原型好还是类好?都是用来给对象分类的

prototype语法

function Square(width){ 
  this.width = width
}
Square.prototype.getArea = function(){ //设置共用属性(getArea)
  return this.width * this.width 
}
Square.prototype.getLength = function(){
  return this.width * 4
}

class语法

待接受参数(width)写到constructor里,表示它是构造函数

class Square{
  constructor(width){
    this.width=width
  }
}
getArea(){
  return this.width * this.width 
}
getLength(){
  return this.width * 4
}

class语法引入了更多概念

class Square{
static x = 1 //第1个概念.意思是:属性(x)是Square的,Square.x即可使用x
   width = 0 //第2个概念.初始化this.width(宽度默认为0)
  constructor(width){
    this.width=width
  }
}
getArea(){
  return this.width * this.width 
}
getLength(){
  return this.width * 4
}
get area2(){ //第3个概念.只读属性,可以将getArea()写成get area2()。区别是:调用area2时不需要加括号()
  return this.width * this.width 
}

用 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()

circle.getLength()

新手建议

1.这么多新语法怎么学

1’ MDN class文档全部看完,并记下来

2’ 用到再学,实践中学,记得更牢

ES 6的所有新语法 关于类和对象的新语法有对象初始化解构赋值

class 中2种函数写法的区别

语法1

class Person{
    sayHi(name){}
    //等价于
    sayHi: function(name){} 
    //注意,一般我们不在这个语法里使用箭头函数
}

//等价于

function Person(){}
Person.prototype.sayHi = function(name){}

语法2:注意冒号变成了等于号

class Person{
  sayHi = (name)=>{} // 注意,一般我们不在这个语法里使用普通函数,多用箭头函数
}

//等价于

function Person(){
    this.sayHi = (name)=>{}
}

备注:不要强求完全转换成 ES5

大部分class语法都可以转为ES5 语法,但并不是100%能转,有些class语法你意思理解就行,不需要强行转换为 ES5。

class就是声明一个类,类就是用来创建对象的,只需要按照这个语法写就可以了。

如果想要这个对象拥有一些属性呢,就写到constructor里面。

如果想要这个对象拥有一些函数呢,就写在外面。

Previous JS对象