面向对象是现实的抽象方式
- 对象是JavaScript的一个非常重要的概念,这是因为对象可以将多个相关联的数据封装到一起,更好的描述一个事物
- 比如我们可以描述一辆车:Car,具有颜色(color)、速度(speed)、价格(price)、品牌(brand)等等
- 比如我们可以描述一个人:Person,具有姓名(name)、年龄(age)、身高(height)、吃东西的行为(eat)、跑步的行为(run)等
- 用对象描述事物,更有利于我们将现实的事物,抽历程代码中的某个数据结构:
- 所以有一些变成语言就是纯面向对象的变成语言,入java;
- 在实现任何显示抽象是都需要创建一个类,根据类再去创建对象;
JavaScript的面向对象
- JavaScript其实支持多种编程范式,包括函数式编程和面向对象编程
- JavaScript中的对象被设计成一组属性的无序集合,由key和value组成
- key是一个标识符名称,value可以是任意类型,也可以是其他对象或者函数类型
- 如果value是函数,那么我们可以称之为是对象的方法
- 如何创建一个对象?
- 早期使用创建的对象的方法最多的是使用Objcet类,并且使用new关键字来创建一个对象
- 后来很多开发者为了方便,都是直接使用字面量的形式来创建对象
//创建一个空的对象
var obj1 = new Object()
obj1.name = "aky"
obj1.age = 18
obj1.height = 1.88
obj.eating = function(){
console.log(this.name + "在吃东西")
}
var obj2 = {
name = "aky"
age = 18
height = 1.88
eating = function(){
console.log(this.name + "在吃东西")
}
对属性操作的控制
- 在我们前面的属性都是直接定义在对象内部,或者直接添加到对象内部的
- 但是这样做的时候我们就不能对这个属性进行一些限制:比如这个属性是否可以通过delete删除、这个属性是否在for-in遍历的时候被遍历出来
- 如果我们想要对一个属性比较精确的操作控制,那么我们可以使用属性描述符
- 通过属性描述符可以精确的添加或者修改对象的属性
- 属性描述符需要使用Object.defineProperty来对属性进行添加或者修改;
Object.defineProperty
- Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
Object.defineProperty(obj,prop,descriptor)
- 可接收三个参数:
- obj:要定义属性的对象;
- prop:要定义或者修改的属性的姓名或Symbol;
- descriptor:要定义或修改属性的属性描述符
- 返回值:被传递给函数的对象
- 可接收三个参数:
属性描述符分类
- 属性描述符的类型有两种:
- 数据类型描述符
- 存取类型描述符
标题 | configurable | enumerable | value | writable | get | set |
---|---|---|---|---|---|---|
数据描述符 | 可以 | 可以 | 可以 | 可以 | 不可以 | 不可以 |
存取描述符 | 可以 | 可以 | 不可以 | 不可以 | 可以 | 可以 |
数据属性描述符
- 数据描述符有如下四个特性
- configurable:表示是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将其修改为存取描述符
- 当我们直接直接在一个对象上定义某个属性时,这个属性默认为true
- 当我们通过属性属性描述符定义一个属性时,这个属性默认为false
- enumerable:表示属性是否可以通过for-in或者Object.keys()返回该属性
- 当我们直接在一个对象上定义某个属性时,这个属性默认为true
- 当我们通过属性描述符定义一个属性时,这个属性默认为false
- writable :表示是否可以修改属性的值;
- 当我们直接在一个对象上定义某个属性时,这个属性默认为true
- 当我们通过属性描述符定义一个属性时,这个属性默认为false
- value :属性的value值,读取属性是返回的值,修改属性时,会对其进行修改
- configurable:表示是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将其修改为存取描述符
存取属性描述符
- 存取数据描述符有如下四个特性
- configurable:表示是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将其修改为存取描述符
- 当我们直接直接在一个对象上定义某个属性时,这个属性默认为true
- 当我们通过属性属性描述符定义一个属性时,这个属性默认为false
- enumerable:表示属性是否可以通过for-in或者Object.keys()返回该属性
- 当我们直接在一个对象上定义某个属性时,这个属性默认为true
- 当我们通过属性描述符定义一个属性时,这个属性默认为false
- get:获取属性时会执行的函数。默认为undefined
- set:修改属性时会执行的函数。默认为undefined
var obj = { name: "aky", _age: 18 } Object.defineProperties(obj,"address", { value: "北京市", configurable: false, enumerable: true, writable: false, }) Object.defineProperties(obj,"age", { configurable: true, enumerable: true, get: function() { return this._address }, set: function(value) { this._address = value } })
- configurable:表示是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将其修改为存取描述符
同时定义多个属性
- Object.defineProperies() 方法直接在一个对象上定义多个 新的属性或者修改现有属性,并且返回该对象
var obj = {
_age = 18
}
Object.defineProperties(obj,{
name:{
writable:true,
value:'aky'
},
age:{
get(){
return this._age
}
}
})
对象方法的补充
- 获取的想的属性描述符
- Object.getOwnPropertyDescriptor9()
- Object.getOwnPropertyDescriptors()
- 禁止对象扩展新属性:
- Object.preventExtensions()
- 冻结对象,不允许修改现有属性:freeze
-
Object.freezes()
-
创建多个对象的方案
- 如果我们现在希望创建一系列的对象:比如person对象
- 包括张三、李四、王五、李雷等等,他们的信息不相同
- 那么采用什么方式创建比较好呢?
- 目前我们已经学习了两种方式:
- new Object方式
- 字面量创建方式
- 这种方式有有一个很大的弊端:创建同样的对象时,需要编写重复的代码;
创建对象的方案—工厂函数
- 我们可以想到的一种创建方式:工厂模式
- 工厂模式:创建一个工厂方法(普通函数),通过该工厂方法我们可以生成想要的对象
function createPerson(name,age,height){
var p = new Object()
p.name = name
p.age =age
p.height = height
p.eating = function(){
console.log(this.name + '在吃东西')
}
return p
}
var p1 = createPerson('张三',18,1.84)
var p2 = createPerson('李四',19,1.85)
var p3 = createPerson('王五',20,1.86)
var p4 = createPerson('李雷',22,1.87)
认识构造函数
- 工厂方法创建对象有一个较大的问题:我们再打印对象时,对象的类型都是Object
- 但是从某种角度来说,这些个对象应该有一个共同的类型
- 下面我们来看一下啊另外一个种模式:构造函数的方法;
- 我们先理解什么是构造函数?
- 构造函数也称之为构造器(constructor),通常是我们在创建对象时调用的函数
- 在其他面向的编程语言中,构造函数时存在于类的一个方法,称之为构造方法
- 但是在JavaScript种构造函数有点不一样
- JavaScript种的构造函数
- 构造函数也是一个普通的函数,从表现形式上来说和普通函数没有任何区别
- 如果一个普通函数被使用new操作符来调用,那么这个函数也称之为构造函数
- 那么new 调用有什么特殊呢?
new 操作符调用的作用
- 如果一个函数被使用new操作符调用了,那么他会执行如下操作:
- 在内存中创建一个新的对象(空对象)
- 将该构造函数的prototype对象赋值给新对象的__proto__
- 构造函数内部的this会指向新创建的对象
- 执行函数体内部代码
- 如果构造函数没有返回非空对象,则返回创建出来的对象
//这是个人手写的new操作符 非常简单的 勿喷 function myNew(constructor,...args){ // 创建一个空对象 let newObj = null // 将对象的隐式原型指向构造函数的显示原型 newObj.__proto__ = constructor.prtotype // 绑定this 并执行函数返回结果 let result = constructor.apply(newObj,args) //判断返回值 if(typeof result === "object" || typeof result === "function" ){ return reslut }else{ return newObj } return newObj }
创建对象的方案—构造函数
- 我们来通过构造函数实现一下:
function Person(name, age, height) {
this.name = name
this.age = age
this.height = height
this.eating = function() {
console.log(this.name + "在吃东西~")
}
}
var p1 = new Person("张三", 18, 1.88)
var p2 = new Person("李四", 20, 1.98)
- 这个构造函数可以确保我们的对象时Person的类型(实际上是constructor的属性)
- 构造函数的缺点:我们需要为每个对象的函数去创建一个函数对象实例
认识对象的原型
- JavaScript当中每个对象都有一个特殊的内置属性[[prototype]] (隐式原型对象),这个特殊的对象可以指向另外一个对象
- 这个对象有什么用呢?
- 当我们通过引用对象的属性key来获取一个value是,他会触发[[get]]的操作
- 这个操作会首先检查该属性是否有对应的属性,如果有的话返回value的值
- 如果对象中没有该属性,那么就会访问[[prototype]]内置属性指向的对象上的属性
- 如果通过字面量直接创建一个对象,这个对象也有这样的属性吗? 如果有,该怎么获取这个属性:
- 答案是有的,只要是对象都会有这样的一个内置属性;
- 获取的方式有两种:
- 方式一:通过对象的__proto__属性可以获取(这个是早期浏览器自己添加的,存在一定的兼容性问题)
- 方式二:通过Object.getPrototypeOf() 方法获取到
函数的原型 prototype
- 所有的函数都有一个prototoype的属性 (显式原型对象)
- 是不是因为函数是一个对象,所以他有prototype的属性呢?
- 不是的,因为他是一个函数,才有了这个特殊的属性
- 而不是他是一个对象,所以才有这个属性
再看new操作符
- 前面说过new操作符的步骤如下:
- 在内存种创建一个新的对象 2.这个对象内部的[[prototype]] (隐式原型对象)属性会被赋值为构造函数的prototype属性
- 那么也就一位置我们通过Person构造含糊创建出来的所有对象的[[prototype]] (隐式原型对象)属性都指向Person.prototype
p1._ proto _ === Person.prototype
p1._ proto _ ===p2._ proto _
创建对象的内存表现
Person.prototype.name ="curry"
//Foo 构造函数
function Foo (){}
//创建实例对象
var f1 = new Foo()
var obj = {
name:'why',
age:18,
height:1.88
}
//将Foo函数的prototype指向新的对象
foo.prototype = obj
// 真实开发中我们可以通过Object.defineProperty方式添加constructor
Object.defineProperty(foo.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: foo
})
constructor属性
- 事实上原型对象上面是有一个属性:constructor
- 默认情况下原型上都会添加一个constructor属性,并且当前的函数对象啊你个
重写原型对象
- 如果我们需要在原型上添加过多的属性,我们可以重新整个原型对象
- 前面我们说过,没创建一个函数,就会同时创建他的prototype,这个对象会自动获取constructor属性
- 我们这里相当于给Prototype属性重新赋值了一个新的对象,那么这个新对象的constructor属性就会指向Object构造函数 ,而不是Person构造函数
- 如果我们希望constructor指向Person,那么就得手动添加
- 上面方式虽然可以,但是也会造成constructor的enumerable特性设置了true
-
默认情况下,原生的constructor属性是不可枚举的
-
如果要解决这个问题,就可以使用**Object.defineProperty()**方法
-
创建对象—构造函数和原型组合
- 我们在上一个构造函数的方法创建对象时,有一个弊端:会创建出重复的代码, 比如running函数、eating函数等
-
那么我们就可以将这些方法放到Person.prototype的对象上即可
-