简介
JavaScript的对象是一种无序的集合数据类型,它由若干键值对组成。
//{属性:值 , 属性:值 , ... , 属性:值} 最后一个属性值后面不用加逗号,加了有的低级浏览器会报错
var xiaoming = {
name: '小明',
birth: 1990,
'school': 'No.1 Middle School', //如果属性名包含特殊字符,必须用''扩起来
run:function() {
console.log("Run!");
}
};
声明(创建对象)
1、字面量(单个)
适用于开始时对象内部数据是确定的,比较方便
var obj = {属性名:'属性值'};
es6增强写法
(变量简写)
(函数简写,注意这里不是箭头函数)
2、实例化(单个)
(内置构造函数),适用于开始时不确定对象内部数据,可以后面动态添加
var obj = new Object();
3、工厂函数模式(多个)
虽然可以解决创建多个类似对象的问题,但没有解决对象标识问题(即新创建的对象是什么类型)
// 工厂函数
function createPerson(name, age, job) {
let o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
console.log(this.name);
};
return o;
}
let person1 = createPerson("Nicholas", 29, "Software Engineer");
let person2 = createPerson("Greg", 27, "Doctor");
缺点:都是Object类型,没有对应的类型
4、构造函数模式(多个)
说明
和工厂函数模式差不多,但是要使用new创建。
相比工厂函数,可以确保实例被标识为特定类型。
按照惯例,构造函数名称的首字母都是要大写的,非构造函数则以小写字母开头。为的是一看就能区分,减少沟通成本
// Person 对象的构造器函数constructor,这样传入的参数直接给对象赋值,
// 优势是可以批量创建多个对象,而且有具体的特定类型(如例子的是人person)
// 但每个创建的都有相同数据,浪费内存
function Person(first, last) {
this.firstName = first;
this.lastName = last;
this.call = function(firstName){
this.firstName = firstName;
} ; //给对象新增默认属性
}
// 创建两个 Person 对象
var myFriend = new Person("Bill", "Gates");
var myBrother = new Person("Steve", "Jobs");
Person.nationality1 = "English1";
//这条是无效的,无法在外部给已有的构造器添加新属性和新方法
console.log(person1 instanceof Object); // true
console.log(person1 instanceof Person); // true ,表示person1是Person类型的
console.log(person2 instanceof Object); // true
console.log(person2 instanceof Person); // true
构造函数也是函数
只要不new,就是函数
// 作为构造函数
let person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); // "Nicholas"
// 作为函数调用
Person("Greg", 27, "Doctor"); // 添加到 window 对象,因为没有明确this的情况下,this都是指Global 对象,浏览器就是windows对象
window.sayName(); // "Greg"
// 在另一个对象的作用域中调用
let o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); // "Kristen"
prototype 原型,见下
构造函数的问题
函数重复定义,而且不是同一个函数
function Person(first, last) {
this.firstName = first;
this.lastName = last;
this.sayName = function(firstName){ // 这里的函数,每创建一个对象,函数都是新的,而不是复用
this.firstName = firstName;
} ; //给对象新增默认属性
}
let person1 = new Person("a", "b");
let person2 = new Person("c", "d");
console.log(person1.sayName == person2.sayName); // false
// ========== 可以改为 ==========
// 在外部定义函数
function sayName() {
console.log(this.name);
}
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName; // 内部调用外部函数,这样所有新new的实例都用同一个函数,避免重复创建函数
}
但全局作用域也因此被搞乱了,必须在全局作用域定义多个函数
5、原型和构造函数创建
切记这里用到了this,因此不能用箭头函数
6、二维数组创建对象
7、ES9 展开运算符创建
数组
对象
展开数组、对象,创建对象
(创建出来的对象)
8、ES10 fromEntries
把如下键值对组成的数组,变成对象
原本要通过遍历添加属性的方式生成对象
现在有新方式创建
应用场景:
比如一段网址,问号?后面的是一段query查询数据,现在想提取出来组成对象
直接用URLSearchParams方法提取出来变成数组的方式,然后变成对象
原型 prototype
引用
xiaoming.name; // '小明'
xiaoming.birth; // 1990
xiaoming.age; //访问一个不存在的属性,就是undefined
xiaoming.run(); //调用对象里面的方法
xiaoming['name']; // '小明'
xiaoming['school']; // 'No.1 Middle School',用''扩起来的属性名,无法通过.引用
==============
this
0.this 永远指向一个对象
1.方法中的this,指的是方法所在的对象
var obj = {
name:"yjl",
age:18,
talk:function(){
var x = this.age;
//此处的this,代表这个对象obj,因此this.age = 18 console.log(x);
}
}
普通的函数也是方法,实际上是window对象的方法,因此普通函数里面的this,指的是函数所在的windos对象。
严格模式下,this 为 undefined。
2.单独的情况下,this 指的是全局对象
console.log(this); //网页中,指的是windows对象。小程序中,指的是页面对象。
3.在事件中,this 指的是接收事件的元素。
<button onclick="this.style.display='none'">单击来删除我!</button>
4.显式函数绑定call() 和 apply()
call() 和 apply() 调用对象里面的函数,this表示函数实际所在的对象
可以用来调用所有者对象作为参数的方法。
var person1 = {
fullName: function() {
return this.firstName + " " + this.lastName;
},
country:function(city, country) {
//还可以带参数 return city + "," + country;
}
}
var person2 = {
firstName:"Bill",
lastName: "Gates",
}
var x = person1.fullName.call(person2);
// 会返回 "Bill Gates"
//相当于是打电话让()括号内的对象,作为参数加入到某个对象的函数
var xx = person1.country.call(person2,"Seatle","USA");
//这里就要带参数,不带就是undefined
var z = person1.fullName.apply(person2);
//和call一样
var zz = person1.country.apply(person2,["Seatle","USA"]);
//这里是带数组的 </script>
===============
属性特性(修改删除 / 遍历)
1、数据模式
var obj = {
name: "yjl",
age: 18
}
// 第一种情况
Object.defineProperty(obj, "height", {
// 描述符
value: 1.88,
// 属性height的值
// 默认是undefined
configurable:true,
// 这个属性height的值value,是否可以被修改或删除
// 直接添加属性时,这个值默认是true
// Object.defineProperty 定义时,这个默认是false
Enumerable:true,
// 这个属性的height,是否可以枚举,就是用for-in遍历对象的属性时,或者用Object.keys()遍历对象属性时,是否允许访问到这个属性height
// 直接添加属性时,这个值默认是true
// Object.defineProperty 定义时,这个默认是false
Writable:true,
// 表示是否可以修改属性height的值value
// 直接添加属性时,这个值默认是true
// Object.defineProperty 定义时,这个默认是false
})
// =========== 两种模式只能出现一种 ===============
console.log(obj)
console.log(obj.height)
2、存储模式
var obj = {
name: "yjl",
age: 18
}
// 第二种情况
Object.defineProperty(obj, "weight", {
// 描述符
configurable:true,
// 这个属性height的值value,是否可以被修改或删除
// 直接添加属性时,这个值默认是true
// Object.defineProperty 定义时,这个默认是false
Enumerable:true,
// 这个属性的height,是否可以枚举,就是用for-in遍历对象的属性时,或者用Object.keys()遍历对象属性时,是否允许访问到这个属性height
// 直接添加属性时,这个值默认是true
// Object.defineProperty 定义时,这个默认是false
get:function(){ // 外面通过 obj.weight 读取值时,就会调用这个方法
return this.weight
},
set:function(value){ // 外面通过 obj.weight = xxx 设置值时,就会调用这个方法
this.weight = value // value 就是xxx
},
// get、set,一般用于截获外面调用和设置这个属性的操作,然后进行额外的操作。
// 比如每次读取这个属性时,就打印“读取了这个属性”(执行一些代码)
// 比如Vue2.x就是通过这个实现响应式
})
// =========== 两种模式只能出现一种 ===============
console.log(obj)
console.log(obj.height)
第二种情况还可以简写成如下:
有一点差别是这种默认的方法可以访问得到
在读取对象属性时,会调用获取函数,这个函数的责任就是返回一个有效的值。
只定义getter,不定义setter,意味着这个属性是只读的,不允许修改(如下age)
// 1
const person = {
firstName: "Bill",
lastName : "Gates",
language : "en",
get lang() {
return this.language;
}
};
// 使用 getter 显示来自对象的数据:
document.getElementById("demo").innerHTML = person.lang;
// 2
let person = {
firstName: "Bill",
lastName : "Gates",
age : 37,
};
Object.defineProperty(person, "age", {
get() {
console.log("读取属性age")
return this.language;
}
})
person.age; // 37
// 同时输出 "读取属性age"
只定义setter,不定义getter,属性就是不能读取的
// 1
var person = {
firstName: "Bill",
lastName : "Gates",
language : "en",
set lang(value) {
this.language = value;
}
};
// 使用 set 设置属性:
person.lang = "zh";
// 显示对象的数据:
document.getElementById("demo").innerHTML = person.language;
// 2
let person = {
firstName: "Bill",
lastName : "Gates",
age : 37,
};
Object.defineProperty(book, "year", {
get() {
return this.year_;
},
set(newValue) {
if (newValue > 2017) {
this.year_ = newValue;
this.edition += newValue - 2017;
}
}
});
book.year = 2018;
console.log(book.edition); // 2
例子:计数器
var obj = {
counter : 0,
get reset() { //重置
this.counter = 0;
},
get increment() { //+1
this.counter++;
},
set add(value) { //+n
this.counter += value;
}
};
//Object.defineProperty() 方法可以为对象obj从外部添加getter和setter,和内部写效果一样。
Object.defineProperty(obj, "decrement", {
get : function () {this.counter--;}
});
Object.defineProperty(obj, "subtract", {
set : function (value) {this.counter -= value;}
});
// 操作计数器:
obj.reset;
obj.add = 5;
obj.subtract = 1;
obj.increment;
obj.decrement;
// 显示计数器:
console.log(obj.counter);
不可遍历
如果设置了 Enumerable : false, 正常来说这个属性是不能通过遍历(for循环等)获得的,console.log打印对象时,也不会直接显示。
但是浏览器为了方便我们调试,也打印了出来,但是字体是偏灰色的。
同时定义多个属性
可以同时定义多个属性,就是ty改成ties,另一个方法
获取属性特性
// 获取某一个特性属性的属性描述符
console.log(Object.getOwnPropertyDescriptor(obj, "name"))
console.log(Object.getOwnPropertyDescriptor(obj, "age"))
// 获取对象的所有属性描述符
console.log(Object.getOwnPropertyDescriptors(obj))
禁止添加属性
var obj = {
name: 'why',
age: 18
}
// 禁止对象继续添加新的属性
Object.preventExtensions(obj)
obj.height = 1.88 // 添加失败
obj.address = "广州市" // 添加失败
console.log(obj)
===============
属性操作
1、赋值
ES6 解构赋值
https://www.yuque.com/yejielin/mypn47/huqxc6#VXvrD
如果中途出错,会完成出错前的那部分赋值
2、添加属性
// bad
const a = {};
a.x = 3;
// 如果不可避免要添加新属性,建议用Object.assign(),这个是合并两个对象的方法
const a = {};
Object.assign(a, { x: 3 });
// good
const a = { x: null };
a.x = 3;
ES6动态键名、方法名
键名加中括号[ ] ,表示键名是表达式,计算后的结果才是键名
可计算属性表达式中抛出任何错误都会中断对象创建。
如果计算属性的表达式有副作用,那就要小心了,因为如果表达式抛出错误,那么之前完成的计算是不能回滚的
const nameKey = 'name';
const ageKey = 'age';
const jobKey = 'job';
const methodKey = 'sayName';
let uniqueToken = 0;
function getUniqueKey(key) {
return `${key}_${uniqueToken++}`;
}
let person = {
[nameKey + '_0']: 'Matt', // 这里是可以引用其他变量生成对象属性名
[getUniqueKey(ageKey)]: 27,
[getUniqueKey(jobKey)]: 'Software engineer', // 这里可以引用函数生成属性名
[methodKey](name) {
console.log(`My name is ${name}`);
}
};
console.log(person); // { name_0: 'Matt', age_1: 27, job_2: 'Software engineer' }
独特符号键名 Symbol
https://www.yuque.com/yejielin/mypn47/tq8e2a#984e0115
给obj添加属性
调用属性
获取对象的所有Symbol 属性
获取所有属性和描述
console.log(Object.getOwnPropertyDescriptors(o)); //返回同时包含 常规 和 符号 属性描述符的对象
// {baz: {...}, qux: {...}, Symbol(foo): {...}, Symbol(bar): {...}}
一些方法
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol
3、删除属性
delete xiaoming.age;
4、in 检测是否拥有某一属性
'name' in xiaoming; // true
'grade' in xiaoming; // false
不过要小心,如果in判断一个属性存在,这个属性不一定是xiaoming的,它可能是xiaoming继承得到的:
‘toString’ in xiaoming; //true,因为toString定义在object对象中,而所有对象最终都会在原型链上指向object,所以xiaoming也拥有toString属性
推荐用hasOwnProperty()方法来判断属性是否该对象下的
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false
5、instanceof 检测是否对象的实例
console.log(person instanceof Object); // 变量 person 是 Object 吗?
console.log(colors instanceof Array); // 变量 colors 是 Array 吗?
console.log(pattern instanceof RegExp); // 变量 pattern 是 RegExp 吗?
6、遍历属性
for…in
Object.keys
Object.values
Object.entries
会忽略Symbols 属性
7、拷贝(复制属性)
【目的】:复制一个对象的所有值到另一个对象
【说明】:一看以为很简单,不就是a=b。但对象实际是引用类型,表示的是一个内存地址的值,因此a=b只是把a的内存地址赋值给了b,这样修改a的属性,同样会导致b显示的属性会变化,因为他们本质是同一个对象。
【应用场景】:如Vue组件,你需要把一个初始对象传给子组件,同时我并不想这个组件把我初始值给修改了,因此需要拷贝。
浅拷贝
只拷贝第一层属性,如果第一层有的属性是引用类型(对象,数组这些表示内存地址的),只是把内存地址复制过去
// 1、const 新对象 = Object.assign({}, 旧对象);
let obj1 = { a: 0 , b: { c: 0}};
let obj2 = Object.assign({}, obj1);
// 或者
let obj3 = {...obj1}
log(JSON.stringify(obj2));
// { a: 0, b: { c: 0}}
// 只拷贝了表面一层
obj1.a = 1;
log(JSON.stringify(obj1));// { a: 1, b: { c: 0}}
log(JSON.stringify(obj2));// { a: 0, b: { c: 0}}
// 无法拷贝深层
obj1.b.c = 111;
JSON.stringify(obj1)// "{"a":0,"b":{"c":111}}"
JSON.stringify(obj2)// "{"a":0,"b":{"c":111}}"
// 2、解构赋值拷贝
let person = {
name: 'Matt',
age: 27,
job: {
title: 'Software engineer'
}
};
let personCopy = {};
({
name: personCopy.name,
age: personCopy.age,
job: personCopy.job
} = person);
// 声明 title 变量并将 person.job.title 的值赋给它
let { job: { title } } = person;
console.log(title); // Software engineer
// 因为一个对象的引用被赋值给 personCopy,所以修改
// person.job 对象的属性也会影响 personCopy
person.job.title = 'Hacker'
console.log(person);
// { name: 'Matt', age: 27, job: { title: 'Hacker' } }
console.log(personCopy);
// { name: 'Matt', age: 27, job: { title: 'Hacker' } }
深拷贝
方法一:
但是null、undefined会变成文本,函数也有问题
const b = {b:0}
const obj = { o: 1 ,b};
const copy = JSON.parse(json.stringify(obj))
console.log(copy); // { o: 1,b:0 }
b.b=1
console.log(copy); // { o: 1,b:0 }
方法二:递归拷贝
循环判断对象的每个属性,如果是对象或者数组(引用类型),就进入继续判断,直到是数值、文本、布尔类型,然后拷贝。
相当于每个元素都完全拷贝过来
8、判断两个值是否完全相等 Object.is
对象操作
对象合并 Object.assign
设置原型对象
不建议直接设置原型对象,应该构造时设置上
操作总结
不要使用new Number()、new Boolean()、new String()创建包装对象;
请使用对象字面量 {} 代替 new Object()。
请使用字符串字面量 "" 代替 new String()。
请使用数值字面量代替 Number()。
请使用布尔字面量代替 new Boolean()。
请使用数组字面量 [] 代替 new Array()。
请使用模式字面量代替 new RexExp()。
请使用函数表达式 () {} 代替 new Function()。
var x6 = /()/ // 新的正则表达式对象
用parseInt()或parseFloat()来转换任意类型到number;
用String()来转换任意类型到string,或者直接调用某个对象的toString()方法;
通常不必把任意类型转换为boolean再判断,因为可以直接写if (myVar) {…};
typeof操作符可以判断出number、boolean、string、function和undefined;
判断Array要使用Array.isArray(arr);
判断null请使用myVar === null;
判断某个全局变量是否存在用typeof window.myVar === ‘undefined’;
函数内部判断某个变量是否存在用typeof myVar === ‘undefined’。