JavaScript对象从浅入深
# JavaScript 对象从浅入深
JavaScript 中的所有事物都是对象:字符串、数值、数组、函数...
此外,JavaScript 允许自定义对象(自己创建对象)。
万物皆对象
JavaScript 提供多个内建对象,比如 String、Date、Array 等等。 对象只是带有属性和方法的特殊数据类型。
- 布尔型可以是一个对象。
- 数字型可以是一个对象。
- 字符串也可以是一个对象
- 日期是一个对象
- 数学和正则表达式也是对象
- 数组是一个对象
- 甚至函数也可以是对象
# 对象的分类
1.内建对象
- 由ES标准中定义的对象,在任何的ES实现中都可以使用
- 比如:Math, String, Number, Boolean, Function, Object...
2.宿主对象
- 由js的运行环境提供的对象, 目前来讲主要是由浏览器提供的对象
- 比如BOM(浏览器对象模型) DOM(文档对象模型)
3.自定义对象 (重点难点)
- 由开发人员自己创建的对象
# 创建 JavaScript 对象
方法1:通过 new Object(); 创建一个对象
- 使用new关键词调用的函数,是构造函数constructor
- 构造函数是专门用来创建对象的函数
let person = new Object();
person.name = '阿离王';
person.age = 28;
person.say = function(){
console.log('我会说国语');
}
console.log(person);
2
3
4
5
6
7
方法2:也可以使用对象字面量来创建对象,语法格式如下:
{
键: 值,
键: 值
}
2
3
4
let person = {
name: '阿离王',
age: 28,
say: function(){
console.log('我会说国语');
}
}
2
3
4
5
6
7
方法3:使用函数来构造对象
function person(name,age,say)
{
this.name = name;
this.age = age;
this.say = say;
}
let my = new person("阿离王", 28, function(){ console.log('我会说国语') });
2
3
4
5
6
7
8
在JavaScript中,this通常指向的是我们正在执行的函数本身,或者是指向该函数所属的对象(运行时)
# 读取对象属性的值
console.log(person.name);
console.log(person['say']());
2
通过.属性名, 或者['属性名']的方式来读取对象属性的值
属性名可以随便起
如果要使用特殊的属性名,不能采用.的方式来操作
需要使用另一种方式:
语法:对象["属性名"] = 属性值
读取也需要用这种方式
let obj = {};
obj[123] = 456;
console.log(obj['123']);
2
3
使用[]这种形式去操作属性,使用更加的灵活
在[]中可以直接传递一个变量,这样变量的值是多少就会读取哪个属性
let obj2 = {
name: 'yu',
age: 28
};
let val = 'name';
console.log(obj2[val]);
2
3
4
5
6
使用[]
如果读取对象中没有的属性时,不会报错而是返回undefined
如果读取对象中没有的属性的属性时,会报错
console.log(person.haha); //undefined
console.log(person.haha.ha); // Cannot read property 'ha' of undefined
2
# 设置(修改)对象属性的值
person.name = '帅哥';
person.say = function(){
console.log('我会说英语');
}
console.log(person.name);
console.log(person['say']());
2
3
4
5
6
# 添加对象属性
person.hobby = '跑步';
console.log(person.hobby);
2
# 遍历对象属性
for...in 语句循环遍历对象的属性。
语法
for (key in object)
{
执行的代码……
}
2
3
4
注意: for...in 循环中的代码块将针对每个属性执行一次。
let person = {
name: '阿离王',
age: 28,
say: function(){
console.log('我会说国语');
}
}
for (let key in person) {
console.log(key, person[key]);
}
2
3
4
5
6
7
8
9
10
# 删除对象的属性
语法: delete 对象.属性名
delete person.name;
console.log(person.name); //undefined
2
# in 运算符
通过该运算符可以检查一个对象中是否含有指定的属性,如有则返回true,没有则返回false
语法:"属性名" in 对象
let obj3 = {name: 'yu'};
console.log('name' in obj3);
console.log('age' in obj3);
2
3
使用in检查对象中是否含有某个属性时, 如果对象中没有该属性,但是原型中有,也会返回true
function Person(){}
Person.prototype.haha = '哈哈';
let obj = new Person();
//使用in检查对象中是否含有某个属性时, 如果对象中没有该属性,但是原型中有,也会返回true
console.log("haha" in obj); // true
2
3
4
5
6
# 使用工厂方法创建对象
之前创建对象时,直接let obj = {}
;
let obj = {
name: '阿离王',
age: 28,
sayName: function(){
console.log(this.name);
}
}
let obj1 = {
name: '张三',
age: 20,
sayName: function(){
console.log(this.name);
}
}
let obj2 = {
name: '李四',
age: 19,
sayName: function(){
console.log(this.name);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
上面的例子,定义对象,每个对象的属性是不是都一样,有共同点,这样创建类似的对象就得写很多重复得代码
我们可以把他们共同的部分抽取出来,封装个工厂函数来创建对象。
来我们开始造人
/*
使用工厂方法创建对象
通过该方法可以批量的创建对象
*/
function createPerson(name, age){
//创建一个新对象
let obj = new Object();
obj.name = name;
obj.age = age;
obj.sayName = function(){
console.log(this.name);
}
return obj;
}
let obj1 = createPerson('阿离王', 28);
let obj2 = createPerson('张三', 20);
let obj3 = createPerson('李四', 19);
console.log(obj1);
console.log(obj2);
console.log(obj3.sayName());
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
以上的写法是不是灵活很多,可以很方便的批量创建多个对象
# 构造函数
- 使用new关键词调用的函数,是构造函数(constructor)
- 构造函数是专门用来创建对象的函数
- 构造函数就是一个普通的函数,
- 不同的是,构造函数习惯上函数名首字母大写(不成文规范)
- 构造函数和普通函数的区别就是调用方式的不同
- 普通函数是直接调用,而构造函数需要使用new关键字来调用
构造函数的执行流程:
- 1.立刻创建一个新的对象(在内存开辟一个新空间)
- 2.将新建的对象设置为函数中的this的指向新建的对象,在构造函数中可以使用this来引用新建的对象
- 3.逐行执行函数中的代码
- 4.将新建的对象作为返回值返回
使用同一个构造函数创建的对象,我们称为一类对象
,也将一个构造函数称为一个类
我们将通过一个构造函数创建的对象,称为是该类的实例, new 一个构造函数,我们称为实例化一个对象出来
this指向:
- 1.当以函数的形式调用时,this是window
- 2.当以方法的形式调用时,谁调用方法this就是谁
- 3.当以构造函数的形式调用时,this就是新创建的那个对象
function Person(name, age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name);
}
}
let per = new Person('阿离王', 28);
let per1 = new Person('张三', 20);
let per2 = new Person('李四', 19);
console.log(per);
console.log(per1);
console.log(per2.sayName());
function Dog(){
}
let dog = new Dog();
console.log(dog);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
使用instanceof可以检查一个对象是否是一个类的实例
语法:对象 instanceof 构造函数
如果是返回true
, 否则返回false
console.log(per instanceof Person); // true
console.log(dog instanceof Dog); // true
2
所有的对象都是Object的后代
所以任何对象和Object做 instanceof 检查都会返回true
console.log(per instanceof Object); // true
console.log(dog instanceof Object); // true
2
构造函数优化
/*
创建一个Person构造函数
在Person构造函数中,为每一个对象都添加了sayName方法
sayName方法是在构造函数内部创建的,也就是构造函数每执行一次就会创建一个新的sayName方法
这样导致了构造战术执行一次就创建一个新的方法
执行10000次就创建1万个一模一样的方法,这个完全没必要的,浪费性能内存
我们可以使所有的对象共享一个方法(设置全局方法)
*/
function Person(name, age){
this.name = name;
this.age = age;
// this.sayName = function(){
// console.log(this.name);
// }
this.sayName = sayName();
}
// 将sayName函数在全局作用域中定义
function sayName(){
console.log(this.name);
}
let per = new Person('阿离王', 28);
let per1 = new Person('张三', 20);
let per2 = new Person('李四', 19);
console.log(per);
console.log(per1);
console.log(per2.sayName());
console.log(per.sayName === per1.sayName); // true
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
# 原型prototype
- 我们所创建的每一个函数,解析器都会向函数添加一个属性
prototype
,这个属性对应着一个对象,这个对象就是我们所谓的原型对象 - 如果函数作为普通函数调用时,
prototype
没有任何作用 - 当函数以构造函数的形式调用时,他它所创建的对象中都会有一个隐含的属性(
__proto__
),指向该构造函数的原型对象,我们可以通过__proto__
来访问该对象的构造函数的原型对象 - 原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象
- 我们可以将对象中共有的内容,统一设置到原型对象中
- 当我们访问对象的一个属性或方法时,它会先去对象自身中寻找,如果有直接使用,没有则去原型链对象中寻找,找到则使用,没有就一层层往上找,直到找到Object对象(对象的祖宗), 在Object对象还找不到,则返回undefined
- 以后我们创建构造函数时,可以将这些实例化出来的对象共有的属性和方法,统一添加到构造函数的原型中,
- 这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了
在构造函数优化那块代码,在全局作用域定义了sayName函数,污染了全局作用域的命名空间,而且定义在全局作用域中也很不安全。
我们可以在Person的原型中添加sayName方法
/*
将sayName函数在全局作用域中定义
这个函数sayName在全局作用域定义,污染了全局作用域的命名空间
而且定义在全局作用域中也很不安全 (容易给其他程序员写相同名覆盖)
*/
function Person(name, age){
this.name = name;
this.age = age;
// this.sayName = sayName();
}
// function sayName(){
// console.log(this.name);
// }
//向原型中添加sayName方法
Person.prototype.sayName = function(){
console.log(this.name);
}
let per = new Person('阿离王', 28);
let per1 = new Person('张三', 20);
let per2 = new Person('李四', 19);
console.log(per);
console.log(per1);
console.log(per2.sayName());
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
- 原型对象也是对象,所以它也有原型
- 当我们使用一个对象的属性或方法时,会先在自身中寻找
- 自身中如果有,则直接使用
- 如果没有则去原型对象中寻找,如果原型对象中有,则使用
- 如果没有则去原型的原型中寻找,直到找到Object对象的原型,
- Object对象的原型是null(没有了), 如果在Object中依然找不到,则返回undefined
这个在原型中不断的寻找原型的原型,直到Object, 这样形成一条链,我们称这条链为,原型链
使用in检查对象中是否含有某个属性时, 如果对象中没有该属性,但是原型中有,也会返回true
function Person(name, age){
this.name = name;
this.age = age;
// this.sayName = sayName();
}
Person.prototype.haha = '哈哈';
let obj = new Person();
console.log("haha" in obj);
2
3
4
5
6
7
8
这时可以使用对象**hasOwnProperty()**方法来检查自身对象中是否含有该属性
console.log(obj2.hasOwnProperty("name"));
# 对象的继承
# Object.defineProperty
给对象添加一个属性并指定该属性的配置。
语法:Object.defineProperty(obj, prop, descriptor)
参数:
- obj 要定义属性的对象。
- prop 要定义或修改的属性的名称或 Symbol 。
- descriptor 要定义或修改的属性描述符。
返回值:被传递给函数的对象。
# descriptor 描述
descriptor的值默认都是false
value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。
configurable
configurable 特性表示对象的属性是否可以被删除,以及除 value 和 writable 特性外的其他特性是否可以被修改 默认为 false。
enumerable
是否可以被枚举,(for in Object.keys) , true可以, false不可以
writable
值是否可以重写, true可以 | false(默认)
# 数据描述符
- value
- writable
# 存取描述符
- get
- set
描述符的键值value 或 writable 和 get 或 set,不能同时存在 ,否则会产生一个异常。
let obj = {};
let initValue = 'hello world';
Object.defineProperty(obj, 'key', {
get: function () {
// 当获取值的时候触发的函数
return initValue;
},
set: function (value) {
// 当设置值的时候触发函数,设置的新值通过value获取
initValue = value;
},
// value: '值',
// writable: true, // 值是否可以重写, true | false(默认)
enumerable: true, // 目标是否可以被枚举 true | false(默认)
configurable: false, // 目标属性是否可以被删除,或是否可以再次修改特性 true | false
});
//注意:get或set不是必须成对出现,如没设置方法 get set 的默认值位 undefined
console.log(obj.key); // hello world
obj.key = 'aa';
console.log(obj); // {} 使用set, get的时候,Object.defineProperty()设置的对象属性在控制台要点击展开才看到,而且比其他对象多了set, get函数
console.log(obj.key); // aa
for (const key in obj) {
console.log(key);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
这里大家注意的就是,使用set, get的时候,Object.defineProperty()设置的对象属性在控制台要点击展开才看到,而且比其他对象多了set, get函数
# Object.freeze
方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。
let obj = {name: 'yu'};
Object.freeze(obj);
obj.name = 'yy';
console.log(obj); // {name: 'yu'} 改变不了
delete obj.name;
console.log(obj); // {name: 'yu'} 删除不了
obj.age = 28;
console.log(obj); // {name: 'yu'} 改变不了
console.log(Object.isFrozen(obj)); // true 是冻住了的对象
Object.defineProperty(obj, 'name', { // 报错
value: 12
});
console.log(obj);
2
3
4
5
6
7
8
9
10
11
12
13
被冻结的对象是不可变的。但也不总是这样。下例展示了冻结对象不是常量对象(浅冻结)。
obj1 = {
internal: {}
};
Object.freeze(obj1);
obj1.internal.a = 'aValue';
obj1.internal.a // 'aValue'
2
3
4
5
6
7
8
// 深冻结函数.
function deepFreeze(obj) {
// 取回定义在obj上的属性名
var propNames = Object.getOwnPropertyNames(obj);
// 在冻结自身之前冻结属性
propNames.forEach(function(name) {
var prop = obj[name];
// 如果prop是个对象,冻结它
if (typeof prop == 'object' && prop !== null)
deepFreeze(prop);
});
// 冻结自身(no-op if already frozen)
return Object.freeze(obj);
}
obj2 = {
internal: {}
};
deepFreeze(obj2);
obj2.internal.a = 'anotherValue';
obj2.internal.a; // undefined
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
在javascript中, 对象冻结后, 没有办法再解冻, 只能通过克隆一个具有相同属性的新对象, 通过修改新对象的属性来达到目的.
可以这样:
ES6 Object.assign({}, frozenObject);
lodash _.assign({}, frozenObject);
2