JavaScript对象
一、对象的创建
- 使用Object类,并且使用new关键字来创建一个对象;
- 通过字面量的形式来创建对象;
二、对属性操作的控制
属性都是直接定义在对象内部,或者直接添加到对象内部的: 但是这样来做的时候我们就不能对这个属性进行一些限制:比如这个属性是否是可以通过delete删除的?这个 属性是否在for-in遍历的时候被遍历出来呢?
想要对一个属性进行比较精准的操作控制,那么我们就可以使用属性描述符。
- 通过属性描述符可以精准的添加或修改对象的属性;
- 属性描述符需要使用 Object.defineProperty 来对属性进行添加或者修改;
1.Object.defineProperty
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此 对象。
Object.defineProperty(obj, prop, descriptor)
三个参数:
- obj:要定义属性的对象;
- prop:要定义或修改的属性的名称或 Symbol;
- descriptor:要定义或修改的属性描述符
**拓展:**同时定义多个属性:
Object.defineProperties() 方法直接在一个对象上定义 多个 新的属性或修改现有属性,并且返回该对象。
var obj = {
_age: 18
}
Object.defineProperties(obj, {
name: {
writable: true,
value: 'tjx'
},
age: {
get: function () {
return this._age
}
}
})
2.属性描述符分类
(1)数据属性描述符
- 1.configurable:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性 描述符
- 当我们直接在一个对象上定义某个属性时,这个属性的configurable为true;
- 当我们通过属性描述符定义一个属性时,这个属性的configurable默认为false;
- 2.enumerable:表示属性是否可以通过for-in或者Object.keys()返回该属性;
- 当我们直接在一个对象上定义某个属性时,这个属性的enumerable为true;
- 当我们通过属性描述符定义一个属性时,这个属性的enumerable默认为false;
- 3.writable:表示是否可以修改属性的值;
- 当我们直接在一个对象上定义某个属性时,这个属性的writable为true;
- 当我们通过属性描述符定义一个属性时,这个属性的writable默认为false;
- 4.value:属性的value值,读取属性时会返回该值,修改属性时,会对其进行修改;
- 默认情况下这个值是undefined;
// 1.直接在一个对象上定义某个属性时
var obj = {
name: 'tjx',
age: 18,
height: 1.88
}
// 当我们直接在一个对象上定义某个属性时,这个属性的configurable为true,所以默认是可以配置的
delete obj.name // { age: 18, height: 1.88 }
console.log(obj);
for (var key in obj) {
console.log(key); // age height
}
//获取对象的属性
console.log(Object.keys(obj)); // [ 'age', 'height' ]
obj.name = 'kobe'
console.log(obj); // { age: 18, height: 1.88, name: 'kobe' }
console.log("***********************");
// 2.通过属性描述符定义一个属性时
Object.defineProperty(obj, 'address', {
// configurable: false,
// enumerable: true,
// writable: false,
value: '成都'
})
// 2.1 测试enumerable为false
// 这种方式访问时看不到属性 当enumerable为true时可以访问看到属性
console.log(obj); // { age: 18, height: 1.88, name: 'kobe' } 没有address属性
console.log(Object.keys(obj)); // [ 'age', 'height', 'name' ]
for (var key in obj) {
console.log(key); // age height name
}
// 这种方式是可以访问的
console.log("address" in obj); // true
console.log(obj.hasOwnProperty('address')); // true
console.log(obj.address); // 成都
// 2.2测试writable,修改address的值
obj.address = "广州"
console.log(obj.address); // 成都 writable默认为false不可修改
// 2.3 测试configurable
// 不可删除
delete obj.address
console.log(obj.address); // 成都
// 不可以重新修改, 一下代码会报错
Object.defineProperty(obj, 'address', {
configurable: true
})
console.log(obj.address);
(2)存储属性描述符
- 1.configurable:表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性 描述符
- 当我们直接在一个对象上定义某个属性时,这个属性的configurable为true;
- 当我们通过属性描述符定义一个属性时,这个属性的configurable默认为false;
- 2.enumerable:表示属性是否可以通过for-in或者Object.keys()返回该属性;
- 当我们直接在一个对象上定义某个属性时,这个属性的enumerable为true;
- 当我们通过属性描述符定义一个属性时,这个属性的enumerable默认为false;
- 3.get:获取属性时会执行的函数。默认为undefined
- 4.set:设置属性时会执行的函数。默认为undefined
var obj = {
name: 'tjx',
age: 19
}
var address = "成都"
Object.defineProperty(obj, 'address', {
configurable: true,
enumerable: true,
get: function () {
// 当address被调用时触发get里面的函数
console.log("getter");
return address
},
set: function (value) {
address = value
// 当address的值被改时调用set里的函数
console.log("setter");
}
})
console.log(obj.address); // 成都
obj.address = '杭州'
console.log(obj.address); // 杭州
三、对象方法补充
1.获取对象的属性描述符
- getOwnPropertyDescriptor
- getOwnPropertyDescriptors
2.禁止对象扩展新属性
禁止对象扩展新属性:preventExtensions;
给一个对象添加新的属性会失败(在严格模式下会报错);
3.不允许配置和删除属性
密封对象,不允许配置和删除属性:seal;
- 实际是调用preventExtensions
- 并且将现有属性的configurable:false
4.不允许修改现有属性
冻结对象,不允许修改现有属性: freeze
- 实际上是调用seal
- 并且将现有属性的writable: false
四、创建多个对象的方案
1.工厂模式
function createPerson(name, age, height, address) {
var p = new Object()
// 总是提示 类型“Object”上不存在属性“***” 但是不影响执行,可能这种写法不支持了把
p.name = name
p.age = age
p.height = height
p.address = address
p.eating = function() {
console.log(this.name + '吃东西--');
}
p.runing = function() {
console.log(this.name + '在跑步--');
}
return p
}
var p1 = createPerson('张三', 10, 1.3, "成都")
var p2 = createPerson('sadf', 10, 1.3, "成都")
var p3 = createPerson('sdfsd', 10, 1.3, "成都")
2.构造函数
工厂方法创建对象有一个比较大的问题:我们在打印对象时,对象的类型都是Object类型 ,但是从某些角度来说,这些对象应该有一个他们共同的类型; 下面我们来看一下另外一种模式:构造函数的方式;
function Person(name, age, height, address) {
this.name = name
this.age = age
this.height = height
this.address = address
this.eating = function() {
console.log(this.name + '吃东西--');
}
this.runing = function() {
console.log(this.name + '在跑步--');
}
}
var p1 = new Person('张三', 10, 1.3, "成都")
var p2 = new Person('sadf', 10, 1.3, "成都")
var p3 = new Person('sdfsd', 10, 1.3, "成都")
(1)认识构造函数
JavaScript中的构造函数是怎么样的?
- 构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别;
- 那么如果这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数;
(2)new操作符调用的作用
- 在内存中创建一个新的对象(空对象);
- 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性;(后面详细讲);
- 构造函数内部的this,会指向创建出来的新对象;
- 执行函数的内部代码(函数体代码);
- 如果构造函数没有返回非空对象,则返回创建出来的新对象;
但是构造函数就没有缺点了吗?
答:构造函数也是有缺点的,它在于我们需要为每个对象的函数去创建一个函数对象实例;
3. 构造函数和原型组合
在上一个构造函数的方式创建对象时,有一个弊端:会创建出重复的函数,比如running、eating这些函数 ;
那么有没有办法让所有的对象去共享这些函数呢?
可以,将这些函数放到Person.prototype的对象上即可;
function Person(name, age, height, address) {
this.name = name,
this.age = age
this.height = height
this.address = address
}
Person.prototype.eating = function() {
console.log(this.name + "在吃");
}
Person.prototype.running = function() {
console.log(this.name + "在跑");
}
var p1 = new Person('why', 18, 1.99, '北京')
var p2 = new Person('士大夫', 18, 1.99, '北京')
p1.eating()
p2.running()
五、原型与原型链
1.隐式原型
JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]],即
__proto__
,这个属性是一个普通的对象,叫隐式原型,也叫对象的原型,这个特殊的对象可以指向另外一个对象(显式原型)。
- 所有对象的
__proto__
属性指向它的构造函数的prototype属性; - 所有的引用数据类型都有
__proto__
属性; - 通过 Object.getPrototypeOf 方法可以获取到原型;
2.显式原型
所有的函数都有一个prototype属性,这个属性是一个普通对象,叫显式原型,也就是我们所说的原型对象。
- 每当定义一个函数数据类型(如
Object
,Function
,Array
,Date
等)时都会自带一个prototype属性;
// 对象
var obj1 = new Object();
console.log(obj1.__proto__); // [Object: null prototype] {}
console.log(obj1.prototype); // undefined 不能直接获取prototype
console.log(Object.getPrototypeOf(obj1)); // [Object: null prototype] {}
console.log(obj1.__protp__ === obj1.prototype); // true
console.log(obj1.__protp__ === Object.prototype); // false
var obj2 = {}
console.log(obj2.__proto__); // [Object: null prototype] {}
console.log(obj2.__protp__ === obj2.prototype); // true
console.log(obj2.__protp__ === Object.prototype); // false
console.log("************");
// 其他引用数据类型
var arry1 = new Array();
console.log(arry1.__proto__); // Object(0) []
console.log(arry1.prototype); // undefined
console.log(arry1.__proto__ === Array.prototype); // true
console.log(arry1.__proto__ === arry1.prototype); // false
console.log(Object.getPrototypeOf(arry1)); // Object(0) []
var arry2 = []
console.log(arry2.__proto__); // Object(0) []
console.log(arry2.__proto__ === arry2.prototype); // false
console.log(arry2.__proto__ === Array.prototype); // true
3.constructor属性
默认情况下原型对象(显式原型)上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象;
function Person() {
}
var p1 = new Person();
console.log(Person.prototype.constructor); // [Function: Person]
console.log(p1.__proto__.constructor); // [Function: Person]
console.log(p1.__proto__.constructor.name); // Person
4.图解
(1)创建对象的内存表现
当new个Person时,p1、p2会有一个隐式原型(
__proto__
)指向Person的显示原型(prototype)。
(2)将原型对象赋值为新的对象
function Foo() {
}
Foo.prototype = {
name: 'tjx',
age: 10,
height: 1.88,
constructor: Foo,
}
var f = new Foo();
console.log(Object.getPrototypeOf(f)); // { name: 'tjx', age: 10, height: 1.88, constructor: [Function: Foo] }
console.log(f.__proto__); // { name: 'tjx', age: 10, height: 1.88, constructor: [Function: Foo] }
console.log(Object.getPrototypeOf(Foo)); // {} 空对象的原因:Foo的prototype属性的enumerable为false,不可枚举,即查看不了
console.log(Object.getOwnPropertyDescriptors(Foo)); // 输出如下:
// {
// length: { value: 0, writable: false, enumerable: false, configurable: true },
// name: {
// value: 'Foo',
// writable: false,
// enumerable: false,
// configurable: true
// },
// arguments: {
// value: null,
// writable: false,
// enumerable: false,
// configurable: false
// },
// caller: {
// value: null,
// writable: false,
// enumerable: false,
// configurable: false
// },
// prototype: {
// value: {
// name: 'tjx',
// age: 10,
// height: 1.88,
// constructor: [Function: Foo]
// },
// writable: true,
// enumerable: false,
// configurable: false
// }
// }
5.原型链
原型与原型层层线连接的过程称为原型链,原型链是通过
__proto__
连接起来的。
function foo(name,age) {
this.name = name
this.age = age
}
Object.prototype.toString = function () {
console.log(this.name + ':' + this.age);
}
var fn = new foo('小明', 19)
fn.toString(); // 小明:19
console.log(fn.toString === foo.prototype.__proto__.toString); // true
console.log(fn.__proto__ === foo.prototype); // ture
console.log(foo.prototype.__proto__ === Object.prototype); // ture
console.log(Object.prototype.__proto__ === null); // ture
6.通过原型链实现继承
7.对象的方法补充
- hasOwnProperty:对象是否有某一个属于自己的属性(不是在原型上的属性)
- in/for in 操作符: 判断某个属性是否在某个对象或者对象的原型上
- instanceof:用于检测构造函数的pototype,是否出现在某个实例对象的原型链上;
- isPrototypeOf:用于检测某个对象,是否出现在某个实例对象的原型链上;