对象需要分类吗?
我们可以通过设定一个例子,根据思路写出代码来,得到答案。
正方形拥有三个属性分别为:边长,周长和面积。以下为求正方形的三个属性代码
代码1
let square = {
width: 5,
getArea(){
return this.width * this.width
},
getLength(){
return this.width * 4
}
如果想求得12个正方形的边长和周长,那么需要重复12次以上的代码。
let square2 = {
width: 6,
getArea(){
return this.width * this.width
},
getLength(){
return this.width * 4
}
*****
代码2
上面的方法太笨了,我们可以使用for循环来解决,来新建12个对象
let squareList = [];
for(let i = 0; i<12 ; i++){
squareList(i) = {
width: 5;
getArea(){
return this.width * this.width
},
getLength(){
return this.width * 4
}
}
}
但是如果,正方形的宽度不全都为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: witdthList[i];
getArea(){
return this.width * this.width
},
getLength(){
return this.width * 4
}
}
}
代码2占有内存, 这次的代码比代码1更精简,但是也不够好,因为占用了太多内存。因为会有square[i}会每次新建一个对象,占有内存,其中含有2个函数getArea()和getLength()。12个新建对象,会产生24对getArea()和getLength()。

代码3
这次我们可以选择使用原型,将12个对象的共有属性放在原型里。代码为:
let squareList = [];
let widthList = [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++){
squareList[i] = Object.create(squarePrototype)
squareList[i].width = widthList[i]
}
这次的代码比上次更节省内存,但是问题在于创建squareList的代码太分散了。
代码4
这次我们就把代码抽离到一个函数里,然后就调用函数,也叫做封装。代码为:
let squareList = [];
let widthList = [5,6,5,6,5,6,5,6,5,6]
// 创建函数封装,这个函数为构造函数
function createSquare(width){
// 以 squarePrototype 为原型新建空的对象
let obj = Object.create(squarePrototype);
obj.with = 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))
}
代码5:
因为squarePrototype原型和createSquare函数还是分散的,我们可以继续提高,把函数和原型组合在一起。代码为:
let squareList = [];
let widthList = [5,6,5,6,5,6,5,6,5,6]
function createSquare(width){
// 注意:这里不是先使用再定义。这里也属于定义
let obj = Object.create(createSquare.squarePrototype);
obj.with = 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] = creatSquare(widthList[i]);
// constructor 可以知道是谁构造了这个对象
console.log(squareList[i].constructor)
}
代码6:
代码到这里就已经完美了,为了最终固定下来,我们来通过使用new操作符重写这段代码。
let squareList = [];
let widthList = [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)
}
总结
代码对比
- 最后的代码6和代码5相比,简化了代码,二者唯一的区别是代码6调用了
new - 通过以上的代码,可知每个函数都有
prototype属性 - 每个
prototype都有constructor, 而constructor为函数自身
new 操作符
- 自动创建空对象
- 自动为空对象关联原型,例如
new x(), 原型地址指定为x.prototype - 自动将空对象作为
this这个关键字运行构造函数 - 自动返回
return this
constructor构造函数
- 例如构造函数
x,本身负责给对象本身添加属性 x.prototype对象负责保存对象的共有属性
代码规范
大小写
- 所有构造函数首字母都为大写
- 所有被构造出来的对象,首字母小写
函数命名
new后面的函数命名都为名词,例如new Person(),new Object()- 其他函数命名都为动词,例如
createSquare(5),createElement(div)
如何确定对象的原型
let obj = new Object()的原型是Object.prototype
let arr = new Array()的原型是Array.prototype
let square = new Square()的原型是Square.prototype
因为new操作符自动为空对象关联原型,原型地址指向X.prototype
结论:JS里唯一的公式
如果a对象是被A构造的,那么a的原型就是A的prototype属性所对应的对象
原型公式
ojbect._proto_ === Object.prototype
对象的_proto_ 等价于其构造函数的prototype
练习题目
问题1
let x = {}
- 请问
x的原型是什么?
答案:x的原型是Object.prototype对应的对象 x._proto_的值是什么?
答案:x._proto_ === Object.prototype- 上面两个问题答案相等吗?
答案:相等
问题2
let square = new Square(5)
- 请问
square的原型是什么?
答案:square的原型是Square.prototype对应的对象 square._proto_的值是什么?
答案:square._proto_ === Square.prototype
问题3
Object.prototype是哪个构造函数构造的?
答案:不知道Object.prototype的原型是什么?
答案:Object.prototype是null,没有原型Object.prototype._proto_?
答案:Object.prototype._proto_ === null
对象需要分类
代码实例
根据上面学到的知识,我们可以写出更多类似的代码需求
Circle圆
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 circle = new Circle(5);
circle.radius;
circle.getArea();
circle.getLength();
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 rect = new Rect(5);
rect.width;
rect.height;
rect.getArea();
rect.getLength();
需要分类的理由
理由一:同类
- 当很多对象拥有共同的属性和行为的时候,可以把它们分为同一类。例如对象
square1和对象square2,都基于Square对象而创建 - 节省多余的代码,高效的创建新的对象
理由二:不同类
- 当很多对象拥有不同的属性和行为的时候,可以把他们分为不同的类。例如
Square,Rect,Circle就是不同的类别。Array和Function也是不同的类别 - 由
Object所创建的对象,是最没有特点的对象
类型VS类
类型
类型是JS数据的分类,分为7种,分为Number数字, String字符串, Boolean布尔值, Symbol符号, Undefined,NaN, Object对象
类
类则是针对对象的分类,有无数种,分为Array,Function,Date,RegExp等等
对象的不同类
Array数组对象
语法
let arr = []; // 新建空数组
let arr = new Array(1,2,3); // 新建数组,分别含有2,3,4
let arr = new Array(3); // 新建空数组,设置长度为3
数组本身的属性
属性名为字符串,例如'1','2','3',同时数组也拥有length属性
数组共有的属性
例如push,pop,shift,unshift,join
Function函数对象
语法
function fn(x,u){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')
函数本身的属性
例如'name'和'length'
函数共有的属性
例如'call','apply','bind'
关于window的解答
window 是谁构造的?
答案:由Window构造,通过代码我们可以知道
window.constructor // 显示为Window()
window.__proto__ === Window.prototype
window.Object是谁构造的?
答案:由window.Function构造,因为所有的函数都是由window.Function构造的,通过代码我们可以知道
window.Object.constructor // 显示为Function()
window.Object.constructor === window.Function
window.Function是谁构造的?
答案: 由window.Function构造的,因为所有的函数都是由window.Function构造的,浏览器和JS引擎构造了Function,然后指定了它的构造者是它自己。
ES6引用了class语法
使用class语法写Square
class Square{
// 变量x在这里无意义,只是显示class语法可以使用Static
static x = 1;
width = 0;
constructor(width){
this.width = width
}
getArea(){
return this.width * this.width
}
getLength:function(){
return this.width * 4
}
// 只读属性
get area2(){
return this.width * this.width
}
}