我们经常调侃:”没有对象,自己new一个“ 。 而在JavaScript中对象是什么呢?
面向对象
面向对象是相对于面向过程来讲的,面向对象方法,把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式(面向对象就是现实的抽象方式)
一句话来说:
面向对象是以对象功能来划分问题,而不是步骤。
面向对象对于我们编程是很重要的一个东西:因为我们在大部分情况下描述一个事物都是从整体来描述的
对象
对象可以将多个相关联的数据封装到一起,更好的描述一个事物:
var girlFriend ={
// key: value
name: '巴咔巴咔',
height: 170,
age: 18
...
}
JavaScript中的对象被设计成一组属性的无序集合,由key和value组成;
- key是一个标识符名称,
- value可以是任意类型,也可以是其他对象或者函数类型,如果值是一个函数,那么我们可以称之为是对象的方法;
创建对象的方法
new Object
new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例
// new一个对象本质是创建一个空对象,然后往里面添加属性和方法
var girlFriend = new Object()
girlFriend。name: '巴咔巴咔'
girlFriend.height = 170
girlFriend.age = 18
字面量创建
var girlFriend ={
name: '巴咔巴咔',
height: 170,
age: 18
...
}
创建单个对象的方法弊端:需要编写的代码重复的地方很多,代码冗杂
创建多个对象的方法
工厂模式 —— 一种常见的设计模式
工厂中的代码写一次便可以复用无数次,能够减少代码量
先写一个工厂函数接收方法,返回对象,接下来就只用传入不同的参数就好了
function createObj(name, age) {
var obj = {}
obj.name = name
obj.age = age
obj.eating = function() {
console.log(this.name + "在吃东西~")
}
return obj
}
var p1 = createObj("张三", 18)
var p2 = createObj("李四", 20)
缺点:工厂模式创建的对象属于Object,无法区分对象类型,这也是工厂模式没有广泛使用的原因
构造函数(constructor)
普通的函数被使用new操作符来调用了,那么这个函数就可以被称之为构造函数
规范: 构造函数定义时首字母大写 (因为是通过调用的方式来确定是否为构造函数,单纯看函数区分不了,所以要通过首字母大写来区分这个函数为构造函数)
function foo() {
...
}
// foo是普通函数
foo()
function Foo() {
...
}
// Foo是构造函数
new Foo()
foo被new调用之后会执行:
- 申请内存,创建对象
- 这个对象内部的
[[prototype]]
(浏览器中提供的__proto__
)会被赋值为该构造函数的prototype属性; - 构造函数内部的this,会指向创建出来的新对象;(new绑定)
- 执行函数的内部代码(函数体代码);
- 如果构造函数没有返回非空对象,则返回创建出来的新对象;(无论如何new 构造函数之后都会返回一个对象值,而普通函数没有返回值则返回undefined)
//手写构造函数
function Test (constructFunction){
let obj = {};
obj.__proto__ = constructFunction.prototype;
return function(){
constructFunction.apply(obj,arguments);
return obj;
}
}
使用:
function Person(name, age) {
this.name = name
this.age = age
this.eating = function() {
console.log(this.name + "在吃东西~")
}
// 不用返回对象,构造函数自动返回
}
var p1 = new Person("张三", 18)
var p2 = new Person("李四", 20)
缺点: 两次构造的对象是不一样的(就算你的操作是一样的,他们构建的对象也是不一样的),因为这个特性每次构造都是一个新的函数对象,就会造成不必要的空间浪费
如何优化上面的构造函数——就要用到“原型”
对象的原型/隐式原型(不会直接使用)
我们每个对象中都有一个 [[prototype]], 这个属性可以称之为 对象的原型(隐式原型)
怎么查看:
__proto__
(浏览器提供):console.log(obj.__proto__
)- Object.getPrototypeOf (ES5之后提供) :console.log(Object.getPrototypeOf (obj))
用途:我们找一个对象里面获取一个属性,在当前对象中找不到就会去原型里面查找。(方便实现继承,在多个对象里面要使用同一个属性或方法时就可以将其放到原型中,而不是放到构造函数中去浪费空间)
函数的原型/显示原型
函数作为对象来说,他也有隐式原型[[prototype]],但函数因为是一个函数,所以他还有一个显示原型prototype
function Obj() {
}
var obj1 = new Obj()
var obj2 = new Obj()
构造函数创建对象的时候会把显示原型赋值给隐式原型:
就是Obj中有prototype且prototype存着原型对象的地址,指向原型对象的地址;构建对象的时候将prototype赋值给obj1和obj2的__proto__
,这样obj1和obj2的__proto__
也指向原型对象(obj2.__proto__
=== Obj.prototype)
🚨注意:Obj.prototype !== Obj.prototype
(一个构造函数的显式原型和自己的隐式原型是不相等的,注意与上面区分)
因为Obj.prototype = { constructor: Obj }
而Obj.__proto__ = Function.prototype
Function.prototype = { constructor: Function }
原型关系:
原型对象中有一个constructor属性,而这个constructor属性指向当前的函数对象(Obj)
现在我们了解了原型的概念,知道了构造函数的弊端是会创建重复的函数,那么就来尝试优化一下构造函数吧:
场景
将重复的函数方法/属性放进原型中再进行调用(注意:得是重复的才能放进原型,不重复的放入会被后来的覆盖,所以一般来说是函数方法放入原型,属性很少)
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.eating = function() {
console.log(this.name + "在吃东西~")
}
Person.prototype.running = function() {
console.log(this.name + "在跑步~")
}
var p1 = new Person("张三", 18)
var p2 = new Person("李四", 20)
p1.eating()
p2.eating()
操作对象的方法
var obj ={
name: '巴咔巴咔',
height: 170,
age: 18
...
}
// 获取属性
console.log(obj.name)
// 给属性赋值
obj.height = 172
// 删除属性
delete obj.name
// 遍历属性
for (var key in obj) {
console.log(key)
}
但有时候我们不想让别人这么轻易地对 ‘对象中的属性’ 进行操作,对别人的操作进行限制,就要用到 Object.defineProperty
Object.defineProperty()
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
**Object.defineProperty(obj,prop,descriptor) 中的参数分别对应 '要定义属性的对象'、'要定义或修改的属性/Symbol'、'属性描述符'**
,返回值:被传递给的函数的对象
而其中的属性描述符又分为数据描述符和存取描述符
configurable | enumerable | value | writable | get | set | |
---|---|---|---|---|---|---|
数据描述符 | T | T | T | T | F | F |
存取描述符 | T | T | F | F | T | T |
- [configurable]:表示属性是否可配置(删除、修改特性、改为存取属性描述符)
- [enumerable]:有可枚举的意思(能否通过for-in或Object.keys( )来返回属性)
- [value]:属性的value值,读取属性时会返回该值,修改属性时,会对其进行修改; 默认情况下这个值是undefined;
- [writable]:表示能否修改属性的值
- [get]:获取属性时会执行的函数。默认为undefined
- [set]:设置属性时会执行的函数。默认为undefined
数据属性描述符:
var obj ={
name: '巴咔巴咔',
height: 170,
age: 18
}
// 数据属性描述符
Object.defineProperty(obj, "address", {
value: "北京市", // 默认值undefined
// 该特性不可删除/也不可以重新定义属性描述符
configurable: false, // 默认值false
// 该特性是配置对应的属性(address)是否是可以枚举
enumerable: true, // 默认值false
// 该特性是属性是否是可以赋值(写入值)
writable: false // 默认值false
})
// 测试configurable的作用
delete obj.address // 报错
// 测试enumerable的作用
for (var key in obj) {
console.log(key) // name height age address
}
// 测试writable的作用
obj.address = "上海市"
console.log(obj.address) // 北京市 (这是一个静默错误)
存取属性符使用场景:
1.隐藏某一个私有属性不希望直接被外界使用和赋值(如下面_address
就被隐藏起来了,别人使用的是address
,虽然说_address
是私有属性,但JS里没有严格意义的私有属性,就是说console.log
还是可以看到,但程序员看到了以下划线开头的就知道是私有属性/方法而不会在外面直接使用它,只能说是默认的规范)
var obj ={
name: '巴咔巴咔',
height: 170,
age: 18,
_address: "北京市"
}
// 存取属性描述符
Object.defineProperty(obj, "address", {
enumerable: true,
configurable: true,
get: function() {
return this._address
},
set: function(value) {
this._address = value
}
})
console.log(obj.address)
obj.address = "上海市"
console.log(obj.address)
2.如果我们希望截获某一个属性它访问和设置值的过程时, 也会使用存储属性描述符(vue中响应式的原理)
var obj ={
name: '巴咔巴咔',
height: '170',
age: '18',
_address: "北京市"
}
Object.defineProperty(obj, "address", {
enumerable: true,
configurable: true,
get: function () {
foo()
return this._address
},
set: function (value) {
bar()
this._address = value
}
})
console.log(obj.address)
obj.address = "上海市"
console.log(obj.address)
function foo() {
console.log("获取了一次address的值")
}
function bar() {
console.log("设置了addres的值")
}
注意:有writable和value就不能有get和set
Object.defineProperties()
上面的Object.defineProperty()
只能定义一个属性,而Object.defineProperties()
可以定义多个属性
Object.defineProperties(obj, {
name: {
configurable: true,
enumerable: true,
writable: true,
value: "玛卡巴卡"
},
age: {
configurable: true,
enumerable: true,
get: function() {
return this._age
},
set: function(value) {
this._age = value
}
}
})
补充
- 获取某一个特性属性的属性描述符 :
console.log(Object.getOwnPropertyDescriptor(obj,"name"))
- 获取对象的所有属性描述符:
console.log(Object.getOwnPropertyDescriptors(obj))
- 禁止对象继续添加新的属性:
Object.preventExtensions(obj)
- 让属性不可以修改(writable: false)
Object.freeze(obj)
......
- hasOwnProperty方法判断对象中是否含有某个属性(不是原型上的属性)
console.log(info.hasOwnProperty("address"))
console.log(info.hasOwnProperty("name"))
6. in 操作符: 判断对象中是否含有某个属性(不管在当前对象还是原型中返回的都是true) console.log("address" in info)
console.log("name" in info)
- instanceof 用于判断构造函数的pototype,是否出现在某个实例对象的原型链上
console.log(stu instanceof Student)
// 判断Student的原型是否出现在stu的原型链上