JS对象需要分类吗?
描述抽象的事物需要分类
JS 需要计算存储抽象的概念
…
从实例看JS对象分类的必要性
一个简单地例子:输出各种形状的面积和周长
先从正方形开始
1
2
3
4
5
6
7
8
9
|
let square = {
width: 5,
getArea(){
return this.width * this.width
},
getLength(){
return this.width * 4
}
}
|
分析正方形有三个属性:边长、面积、周长
来一打
用循环
1
2
3
4
5
6
7
8
9
10
11
12
13
|
let squareList = []
for(let i = 0; i < 12; i++){
squareList[i] = {
width: 5,
getArea(){
return this.width * this.width
},
getLength(){
return this.width * 4
}
}
}
squareList[0].getArea === squareList[1].getArea //false
|
width 要不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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
}
}
}
squareList[0].getArea === squareList[1].getArea //false
|
重复,浪费太多内存,画内存图
内存图,重复的函数
借助原型,将12个对象的共用属性放到原型里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
let squareList = []
let widthList = [5,6,5,6,5,6,5,6,5,6,5,6]
let squarePrototype = { //设置原型
getArea(){
return this.width * this.width
},
getLength(){
return this.width * 4
}
}
for(let i = 0; i < 12; i++){ //循环创建12个对象
squareList[i] = Object.create(squarePrototype) //创建原型指向
squareList[i].width = widthList[i] //添加对应宽度
}
squareList[0].__proto__ === squareList[1].__proto__ //true
squareList[0].getArea === squareList[1].getArea //true
|
代码太分散
把代码抽到一个函数里,然后调用函数
抽离到函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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])
// 1)将不变的属性放入原型。
// 2)用此原型创建空对象,写到构造函数里。
// 3)将不同的属性作为形参传入构造函数。
// 4)循环创建square。
}
|
squarePrototype
原型和createSquare
函数还是分散的
合并,把原型放到函数上
函数和原型结合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
let squareList = []
let widthList = [5,6,5,6,5,6,5,6,5,6,5,6]
function createSquare(width){
==========
++++++++++
let obj = Object.create(createSquare.squarePrototype) //以createSquare.squarePrototype为原型创建空对象
//执行时机,只有调用函数createSquare的时候,才执行 let obj = Object.create(createSquare.squarePrototype)
//先定义,并未执行
//只要在调用createSquare()前,定义好createSquare.squarePrototype
==========
obj.width = width
return obj
}
==========
++++++++++
createSquare.squarePrototype = { //把原型放到函数createSquare的属性上,并把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
上
拿到函数,方便得找到原型;通过原型,找到函数,互相引用
让代码固定下来,直接使用
new
操作符
函数和原型结合(重写)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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 * 4
}
for (let i = 0; i < 12; i++){
squareList[i] = new Square(widthList[i])
console.log(squareList[i].constructor)
}
|
Square.prototype
和new Square
没有一句多余
每个函数都有prototype
属性,是有B.E.意为之
每个prototype
都有constructor
亦然
1
2
3
4
5
6
7
8
9
10
11
12
|
function f1(){}
console.dir(f1)
< ƒ f1()
arguments: null
caller: null
length: 0
name: "f1"
prototype: {constructor: ƒ}
__proto__: ƒ ()
[[FunctionLocation]]: VM45:1
[[Scopes]]: Scopes[2]
|
自带BGM:prototype constructor(-> fn)
1
2
|
function f1(){}
f1.prototype.constructor === f1 //true
|
对比
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
==================
++++++++++++++++++
function createSquare(width){
let obj = Object.create(createSquare.squarePrototype)
obj.width = width
return obj
}
------------------
function Square(width){
this.width = width
}
==================
++++++++++++++++++
createSquare.squarePrototype = {
getArea(){
return this.width * this.width
},
getLength(){
return this.width * 4
},
constructor: createSquare
}
------------------
Square.prototype.getArea = function(){
return this.width * this.width
}
Square.prototype.getLength = function(){
return this.width * 4
}
==================
++++++++++++++++++
let square = createSquare(5)
------------------
let square = new Square(6)
==================
|
函数大小写
上面的代码(+++++)被简化为下面的代码(—–)惟一的区别是要用new
来调用
用new
节省了let obj = Object.create(createSquare.squarePrototype)
和return obj
用this.width = width
代替obj.width = width
.getArea
等共有属性,必须一个个添加到构造函数的prototype
上,不能用赋值操作符,因为每个函数自带prototype.constructor
等属性,不可以被覆盖掉
除非用Object.assign()
小结
new X()
自动做了四件事
- 自动创建空对象
- 自动为空对象关联原型,原型地址为
X.prototype
- 自动将空对象作为
this
关键字运行构造函数
- 自动
return this
用了new
,你只需
- 声明构造函数时,添加自身属性
- 之后在声明的构造函数的原型上添加共有属性
- 用
new
一个构造函数,来声明函数实例
构造函数X
X
函数本身用来给对象本身添加属性
X.prototype
对象用来保存对象的共用属性
1
2
3
4
5
6
7
8
9
10
11
12
|
function Dog(name,color,type){
this.name = name
this.color = color
this.type = type
}
Dog.prototype.bark = function(){console.log('汪汪')}
let dog1 = new Dog('小白','白','白熊')
console.dir(dog1)
dog1.bark()
Dog.prototype.x = '狗子' //共用属性的一般是函数,但其他也可
let dog2 = new Dog()
dog2.x
|
this
目前就片面地理解为指代那个还没被定义的实例化对象
题外话
大小写
- 所有构造函数(专门用于创建对象的函数)首字母大写
- 所有被构造出来的对象,首字母小写
词性
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
的骚操作是故意这么做的
new X()
自动做了四件事
自动创建空对象
自动为空对象关联原型,原型地址为X.prototype
X.prototype
就存着一个地址(比如#309
),把这个地址(#309)放到对象的x.__proto__
中
结论
你是谁构造的,你的原型就是谁的prototype
属性对应的对象
prototype
存的地址对应内存中的原型的对象
说‘prototype
就是原型’并不严谨,prototype
只是存了原型的地址
原型公式
对象.__proto__ === 其构造函数.prototype
1
2
|
window.Object.__proto__ === window.Function.prototype //true
window.Object.__proto__ === Function.prototype //true
|
JS 中 proto 和 prototype 存在的意义是什么? by 方应杭
必须看文档,来确定某个特定的构造函数,传入不同个数的形参分别代表什么,以及来确定可以接受几个参数
1
2
|
let arr1 = new Array(3) // new Array(array.length)
let arr2 = new Array(3,5) // new Array(element0,element1[,...[,elementN]])
|
测试题
难度1
let x = {}
问:
1.x
的原型是什么? Object.prototype
(对应的对象)
2.x.__proto__
的值是什么?
3.上面两个问题是等价的吗? 是等价的: x.__proto__ === Object.prototype
4.用内存图画出x
的所有属性
let x = {}
,x:#909
#909:{__proto__:#309}
window.Object:#101
#101:{prototype:#309,...}
#309:{toString,...}
难度2
let square = new Square(5)
问
1.square
的原型是什么? 原型是Square.prototype
2.square.__proto__
的值是什么?(等价于上一问)
3.用内存图画出x
的所有属性
难度3
问
1.Object.prototype
是哪个函数构造出来的? 没有,不存在这个函数
2.Object.prototype
的原型是什么? 无,无父无母
3.Object.prototype.__proto__
? null
4.用内存图画出来
1
2
3
|
var x = Object.prototype
x.__proto__ === Object.prototype //false
x.__proto__ //null
|
通过Square
理解构造函数、prototype
、new
再看
Square
最终版( ?)
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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()
|
圆形
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function Circle(radius){
this.radius = radius
}
Circle.prototype.getLength = function(){
return this.radius * 2 * Math.PI
}
Circle.prototype.getArea = function(){
/* return this.radius ** 2 * Math.PI */
return Math.pow(this.radius,2) * Math.PI
}
let c1 = new Circle(10)
c1.radius //10
c1.getLength()
c1.getArea()
|
长方形
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function Rect(width,height){
this.width = width
this.height = height
}
Rect.prototype.getArea = function(){
return this.width * this.height
}
Rect.prototype.getAreaLength = function(){
return (this.width + this.height) * 2
}
let rect1 = new Rect(4,5)
rect1.width
rect1.height
rect1.getArea()
rect1.getLength()
|
再次问:对象需要分类吗
对象需要分类的理由
理由一
- 有很多对象拥有一样的属性和行为
- 需要把它们分为同一类,如
square1
和square2
- 这样创建类似对象的时候就很方便
理由二
- 但还是有很多对象拥有其他的属性和行为
- 必须有不同的分类,比如
Square/Circle/Rect
就是不同的分类
Array/Function
也是不同的分类
- 而
Object
创建出来的对象,是最没有特点的对象
类型 V.S. 类
类型
类型是JS数据的分类,目前有8种
五基两空以对象
类
类是针对于对象的分类,可以有无数种
常见的有Array
、Function
、Date
、RegExp
等
数组对象
定义一个数组对象
1
2
3
|
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'
- 注意,属性名没有数字,只有字符串
1
2
|
let arr1 = new Array(1,2,3)
Object.keys(arr1) //["1","2","3"],length属性遍历不到
|
数组对象的共用属性
'push'
/'pop'
/'shift'
/'unshift'
/'join'
- 注意,属性名没有数字,只有字符串
- JavaScript 标准内置对象Array
1
2
3
4
5
6
7
8
9
10
11
12
13
|
let arr1 = new Array(1,2,3)
arr1.push(4) //4
arr1 //[1,2,3,4]
arr1.pop // 4
arr1 //[1,2,3]
arr1.shift // 1 提档、上档,即删除第一个数组元素
arr1 //[2,3]
arr1.unshift(6,5) //2 返回数组新长度length
arr2 = [1,2,3,4]
arr2.join('-') //"1-2-3-4"
arr3 = [3]
arr4 = [4]
arr3.concat(arr4) //[3,4]
|
数组对象比Object对象多一层共有属性
优先用结构上距离近的的同名属性
函数对象
定义一个函数
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')
函数对象自身属性
1
2
3
4
|
let f = new Function('x','y','return x+y')
f.name //"anonymous"
f.length //2
console.dir(f)
|
函数对象共有属性
JS终极一问
window
是谁构造的
Window
- 可以通过
window.constructor
属性看出构造者
window.__proto__ === Window.prototype
,返回true
window.Object
是谁构造的
window.Function
window.Object.constructor
,返回Function(){..}
window.Object.constructor === window.Function
,返回true
window.Object.__proto__ === window.Function.prototype
- 因为所有函数都是
window.Function
构造的
window.Function
是谁构造的
window.Function
window.Function.constructor
window.Function.constructor === window.Function
- 因为所有函数都是
window.Function
构造的
- 并不是自己构造自己,浏览器的JS引擎构造了
Function
,然后指定它的构造者是自己
这个代码被某些前端是认为过时的
1
2
3
4
5
6
7
8
9
|
function Square(width){
this.width = width
}
Square.prototype.getArea = function(){
return this.width * this.width
}
Square.prototype.getLength = function(){
return this.width * 4
}
|
JS prototype
V.S. JS class
你可以不会 class,但是一定要学会 prototype
ES6引入的类的语法
1
2
3
4
5
6
7
8
9
10
11
|
class Square{
constructor(width){ //构造函数
this.width = width
}
getArea(){ //共有属性 最潮写法
return this.width * this.width
}
getLength = function(){ //共有属性 复古写法
return this.width * 4
}//函数的两种写法都对
}
|
开启class
时代
class
语法引入更多概念
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class Square{
static x = 1
width = 0 // 初始化
constructor(width){
this.width = width
}
getArea(){
return this.width * this.width
}
getLength = function(){
return this.width * 4
}
get area2(){ //只读属性 get,调用时只需Square.area2,不用加`()`
return this.width * this.width
}
}
|
学习语法的建议,在实践中学,查类的MDN文档、类的定义、类表达式
整理的 ES6 所有新特性
MDN的 ES6 关于类的语法1
MDN的 ES6 关于类的语法2
MDN的 ES6 关于类的语法3
用class
重写Circle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Circle{
constructor(radius){
this.radius = radius
}
getArea(){
reutrn Math.pow(this.radius,2) * Math.PI
}
getLength(){
reutrn this.radius * 2 * Math.PI
}
let circle = new Circle(5)
circle.radius
circle.getArea()
circle.getLength()
}
|
用class
重写Rect
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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 rect = new Rect(4,5)
rect.width
rect.height
rect.getArea()
rect.getLength()
}
|
原型思路清晰简单,还是类容易上手?都是给对象分类的,想想不同风格之间的差别,面试时可以扯
参考文章
JS 对象分类.pdf
你可以不会 class,但是一定要学会 prototype by 方应杭
JS 的 new 到底是干什么的? by 方应杭
JS 中 proto 和 prototype 存在的意义是什么? by 方应杭
饥人谷整理的 ES6 所有新特性
MDN的 ES6 关于类的语法1
MDN的 ES6 关于类的语法2
MDN的 ES6 关于类的语法3
相关文章
无
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名
- 河
掘
思
知
简