简介

JavaScript的对象是一种无序的集合数据类型,它由若干键值对组成。

  1. //{属性:值 , 属性:值 , ... , 属性:值} 最后一个属性值后面不用加逗号,加了有的低级浏览器会报错
  2. var xiaoming = {
  3. name: '小明',
  4. birth: 1990,
  5. 'school': 'No.1 Middle School', //如果属性名包含特殊字符,必须用''扩起来
  6. run:function() {
  7. console.log("Run!");
  8. }
  9. };

声明(创建对象)

1、字面量(单个)

适用于开始时对象内部数据是确定的,比较方便

  1. var obj = {属性名:'属性值'};

es6增强写法

image.png(变量简写)

image.png(函数简写,注意这里不是箭头函数)

2、实例化(单个)

(内置构造函数),适用于开始时不确定对象内部数据,可以后面动态添加

  1. var obj = new Object();

3、工厂函数模式(多个)

虽然可以解决创建多个类似对象的问题,但没有解决对象标识问题(即新创建的对象是什么类型)
image.png

  1. // 工厂函数
  2. function createPerson(name, age, job) {
  3. let o = new Object();
  4. o.name = name;
  5. o.age = age;
  6. o.job = job;
  7. o.sayName = function() {
  8. console.log(this.name);
  9. };
  10. return o;
  11. }
  12. let person1 = createPerson("Nicholas", 29, "Software Engineer");
  13. let person2 = createPerson("Greg", 27, "Doctor");

缺点:都是Object类型,没有对应的类型

4、构造函数模式(多个)

说明

和工厂函数模式差不多,但是要使用new创建。
相比工厂函数,可以确保实例被标识为特定类型。

按照惯例,构造函数名称的首字母都是要大写的,非构造函数则以小写字母开头。为的是一看就能区分,减少沟通成本

  1. // Person 对象的构造器函数constructor,这样传入的参数直接给对象赋值,
  2. // 优势是可以批量创建多个对象,而且有具体的特定类型(如例子的是人person)
  3. // 但每个创建的都有相同数据,浪费内存
  4. function Person(first, last) {
  5. this.firstName = first;
  6. this.lastName = last;
  7. this.call = function(firstName){
  8. this.firstName = firstName;
  9. } ; //给对象新增默认属性
  10. }
  11. // 创建两个 Person 对象
  12. var myFriend = new Person("Bill", "Gates");
  13. var myBrother = new Person("Steve", "Jobs");
  14. Person.nationality1 = "English1";
  15. //这条是无效的,无法在外部给已有的构造器添加新属性和新方法
  16. console.log(person1 instanceof Object); // true
  17. console.log(person1 instanceof Person); // true ,表示person1是Person类型的
  18. console.log(person2 instanceof Object); // true
  19. console.log(person2 instanceof Person); // true

构造函数也是函数

只要不new,就是函数

  1. // 作为构造函数
  2. let person = new Person("Nicholas", 29, "Software Engineer");
  3. person.sayName(); // "Nicholas"
  4. // 作为函数调用
  5. Person("Greg", 27, "Doctor"); // 添加到 window 对象,因为没有明确this的情况下,this都是指Global 对象,浏览器就是windows对象
  6. window.sayName(); // "Greg"
  7. // 在另一个对象的作用域中调用
  8. let o = new Object();
  9. Person.call(o, "Kristen", 25, "Nurse");
  10. o.sayName(); // "Kristen"

image.png
prototype 原型,见下

构造函数的问题

函数重复定义,而且不是同一个函数

  1. function Person(first, last) {
  2. this.firstName = first;
  3. this.lastName = last;
  4. this.sayName = function(firstName){ // 这里的函数,每创建一个对象,函数都是新的,而不是复用
  5. this.firstName = firstName;
  6. } ; //给对象新增默认属性
  7. }
  8. let person1 = new Person("a", "b");
  9. let person2 = new Person("c", "d");
  10. console.log(person1.sayName == person2.sayName); // false
  11. // ========== 可以改为 ==========
  12. // 在外部定义函数
  13. function sayName() {
  14. console.log(this.name);
  15. }
  16. function Person(name, age, job){
  17. this.name = name;
  18. this.age = age;
  19. this.job = job;
  20. this.sayName = sayName; // 内部调用外部函数,这样所有新new的实例都用同一个函数,避免重复创建函数
  21. }

但全局作用域也因此被搞乱了,必须在全局作用域定义多个函数

5、原型和构造函数创建

image.png
切记这里用到了this,因此不能用箭头函数

6、二维数组创建对象

image.png image.png
image.png image.png

7、ES9 展开运算符创建

image.png 数组
image.png 对象

image.png 展开数组、对象,创建对象
image.png(创建出来的对象)

8、ES10 fromEntries

把如下键值对组成的数组,变成对象
image.png
原本要通过遍历添加属性的方式生成对象
image.png
现在有新方式创建
image.png
image.png

应用场景:
比如一段网址,问号?后面的是一段query查询数据,现在想提取出来组成对象
image.png
直接用URLSearchParams方法提取出来变成数组的方式,然后变成对象
image.png

原型 prototype


JS - 面向对象编程

引用

  1. xiaoming.name; // '小明'
  2. xiaoming.birth; // 1990
  3. xiaoming.age; //访问一个不存在的属性,就是undefined
  4. xiaoming.run(); //调用对象里面的方法
  5. xiaoming['name']; // '小明'
  6. xiaoming['school']; // 'No.1 Middle School',用''扩起来的属性名,无法通过.引用

==============

this

0.this 永远指向一个对象

1.方法中的this,指的是方法所在的对象

  1. var obj = {
  2. name:"yjl",
  3. age:18,
  4. talk:function(){
  5. var x = this.age;
  6. //此处的this,代表这个对象obj,因此this.age = 18 console.log(x);
  7. }
  8. }

普通的函数也是方法,实际上是window对象的方法,因此普通函数里面的this,指的是函数所在的windos对象。

严格模式下,this 为 undefined。

2.单独的情况下,this 指的是全局对象

  1. console.log(this); //网页中,指的是windows对象。小程序中,指的是页面对象。

3.在事件中,this 指的是接收事件的元素。

  1. <button onclick="this.style.display='none'">单击来删除我!</button>

4.显式函数绑定call() 和 apply()

call() 和 apply() 调用对象里面的函数,this表示函数实际所在的对象
可以用来调用所有者对象作为参数的方法。

  1. var person1 = {
  2. fullName: function() {
  3. return this.firstName + " " + this.lastName;
  4. },
  5. country:function(city, country) {
  6. //还可以带参数 return city + "," + country;
  7. }
  8. }
  9. var person2 = {
  10. firstName:"Bill",
  11. lastName: "Gates",
  12. }
  13. var x = person1.fullName.call(person2);
  14. // 会返回 "Bill Gates"
  15. //相当于是打电话让()括号内的对象,作为参数加入到某个对象的函数
  16. var xx = person1.country.call(person2,"Seatle","USA");
  17. //这里就要带参数,不带就是undefined
  18. var z = person1.fullName.apply(person2);
  19. //和call一样
  20. var zz = person1.country.apply(person2,["Seatle","USA"]);
  21. //这里是带数组的 </script>

===============

属性特性(修改删除 / 遍历)

image.png

1、数据模式

  1. var obj = {
  2. name: "yjl",
  3. age: 18
  4. }
  5. // 第一种情况
  6. Object.defineProperty(obj, "height", {
  7. // 描述符
  8. value: 1.88,
  9. // 属性height的值
  10. // 默认是undefined
  11. configurable:true,
  12. // 这个属性height的值value,是否可以被修改或删除
  13. // 直接添加属性时,这个值默认是true
  14. // Object.defineProperty 定义时,这个默认是false
  15. Enumerable:true,
  16. // 这个属性的height,是否可以枚举,就是用for-in遍历对象的属性时,或者用Object.keys()遍历对象属性时,是否允许访问到这个属性height
  17. // 直接添加属性时,这个值默认是true
  18. // Object.defineProperty 定义时,这个默认是false
  19. Writable:true,
  20. // 表示是否可以修改属性height的值value
  21. // 直接添加属性时,这个值默认是true
  22. // Object.defineProperty 定义时,这个默认是false
  23. })
  24. // =========== 两种模式只能出现一种 ===============
  25. console.log(obj)
  26. console.log(obj.height)

2、存储模式

  1. var obj = {
  2. name: "yjl",
  3. age: 18
  4. }
  5. // 第二种情况
  6. Object.defineProperty(obj, "weight", {
  7. // 描述符
  8. configurable:true,
  9. // 这个属性height的值value,是否可以被修改或删除
  10. // 直接添加属性时,这个值默认是true
  11. // Object.defineProperty 定义时,这个默认是false
  12. Enumerable:true,
  13. // 这个属性的height,是否可以枚举,就是用for-in遍历对象的属性时,或者用Object.keys()遍历对象属性时,是否允许访问到这个属性height
  14. // 直接添加属性时,这个值默认是true
  15. // Object.defineProperty 定义时,这个默认是false
  16. get:function(){ // 外面通过 obj.weight 读取值时,就会调用这个方法
  17. return this.weight
  18. },
  19. set:function(value){ // 外面通过 obj.weight = xxx 设置值时,就会调用这个方法
  20. this.weight = value // value 就是xxx
  21. },
  22. // get、set,一般用于截获外面调用和设置这个属性的操作,然后进行额外的操作。
  23. // 比如每次读取这个属性时,就打印“读取了这个属性”(执行一些代码)
  24. // 比如Vue2.x就是通过这个实现响应式
  25. })
  26. // =========== 两种模式只能出现一种 ===============
  27. console.log(obj)
  28. console.log(obj.height)

第二种情况还可以简写成如下:
image.png
有一点差别是这种默认的方法可以访问得到
image.png

在读取对象属性时,会调用获取函数,这个函数的责任就是返回一个有效的值。

只定义getter,不定义setter,意味着这个属性是只读的,不允许修改(如下age)

  1. // 1
  2. const person = {
  3. firstName: "Bill",
  4. lastName : "Gates",
  5. language : "en",
  6. get lang() {
  7. return this.language;
  8. }
  9. };
  10. // 使用 getter 显示来自对象的数据:
  11. document.getElementById("demo").innerHTML = person.lang;
  12. // 2
  13. let person = {
  14. firstName: "Bill",
  15. lastName : "Gates",
  16. age : 37,
  17. };
  18. Object.defineProperty(person, "age", {
  19. get() {
  20. console.log("读取属性age")
  21. return this.language;
  22. }
  23. })
  24. person.age; // 37
  25. // 同时输出 "读取属性age"

只定义setter,不定义getter,属性就是不能读取的

  1. // 1
  2. var person = {
  3. firstName: "Bill",
  4. lastName : "Gates",
  5. language : "en",
  6. set lang(value) {
  7. this.language = value;
  8. }
  9. };
  10. // 使用 set 设置属性:
  11. person.lang = "zh";
  12. // 显示对象的数据:
  13. document.getElementById("demo").innerHTML = person.language;
  14. // 2
  15. let person = {
  16. firstName: "Bill",
  17. lastName : "Gates",
  18. age : 37,
  19. };
  20. Object.defineProperty(book, "year", {
  21. get() {
  22. return this.year_;
  23. },
  24. set(newValue) {
  25. if (newValue > 2017) {
  26. this.year_ = newValue;
  27. this.edition += newValue - 2017;
  28. }
  29. }
  30. });
  31. book.year = 2018;
  32. console.log(book.edition); // 2

例子:计数器

  1. var obj = {
  2. counter : 0,
  3. get reset() { //重置
  4. this.counter = 0;
  5. },
  6. get increment() { //+1
  7. this.counter++;
  8. },
  9. set add(value) { //+n
  10. this.counter += value;
  11. }
  12. };
  13. //Object.defineProperty() 方法可以为对象obj从外部添加getter和setter,和内部写效果一样。
  14. Object.defineProperty(obj, "decrement", {
  15. get : function () {this.counter--;}
  16. });
  17. Object.defineProperty(obj, "subtract", {
  18. set : function (value) {this.counter -= value;}
  19. });
  20. // 操作计数器:
  21. obj.reset;
  22. obj.add = 5;
  23. obj.subtract = 1;
  24. obj.increment;
  25. obj.decrement;
  26. // 显示计数器:
  27. console.log(obj.counter);

不可遍历

如果设置了 Enumerable : false, 正常来说这个属性是不能通过遍历(for循环等)获得的,console.log打印对象时,也不会直接显示。

但是浏览器为了方便我们调试,也打印了出来,但是字体是偏灰色的。
image.png

同时定义多个属性

可以同时定义多个属性,就是ty改成ties,另一个方法
image.png

获取属性特性

  1. // 获取某一个特性属性的属性描述符
  2. console.log(Object.getOwnPropertyDescriptor(obj, "name"))
  3. console.log(Object.getOwnPropertyDescriptor(obj, "age"))

image.png

  1. // 获取对象的所有属性描述符
  2. console.log(Object.getOwnPropertyDescriptors(obj))

image.png

禁止添加属性

  1. var obj = {
  2. name: 'why',
  3. age: 18
  4. }
  5. // 禁止对象继续添加新的属性
  6. Object.preventExtensions(obj)
  7. obj.height = 1.88 // 添加失败
  8. obj.address = "广州市" // 添加失败
  9. console.log(obj)

===============

属性操作

1、赋值

ES6 解构赋值

https://www.yuque.com/yejielin/mypn47/huqxc6#VXvrD
如果中途出错,会完成出错前的那部分赋值

2、添加属性

  1. // bad
  2. const a = {};
  3. a.x = 3;
  4. // 如果不可避免要添加新属性,建议用Object.assign(),这个是合并两个对象的方法
  5. const a = {};
  6. Object.assign(a, { x: 3 });
  7. // good
  8. const a = { x: null };
  9. a.x = 3;

ES6动态键名、方法名

键名加中括号[ ] ,表示键名是表达式,计算后的结果才是键名

可计算属性表达式中抛出任何错误都会中断对象创建。
如果计算属性的表达式有副作用,那就要小心了,因为如果表达式抛出错误,那么之前完成的计算是不能回滚的

  1. const nameKey = 'name';
  2. const ageKey = 'age';
  3. const jobKey = 'job';
  4. const methodKey = 'sayName';
  5. let uniqueToken = 0;
  6. function getUniqueKey(key) {
  7. return `${key}_${uniqueToken++}`;
  8. }
  9. let person = {
  10. [nameKey + '_0']: 'Matt', // 这里是可以引用其他变量生成对象属性名
  11. [getUniqueKey(ageKey)]: 27,
  12. [getUniqueKey(jobKey)]: 'Software engineer', // 这里可以引用函数生成属性名
  13. [methodKey](name) {
  14. console.log(`My name is ${name}`);
  15. }
  16. };
  17. console.log(person); // { name_0: 'Matt', age_1: 27, job_2: 'Software engineer' }

独特符号键名 Symbol

https://www.yuque.com/yejielin/mypn47/tq8e2a#984e0115

image.png
image.png给obj添加属性

image.png调用属性

获取对象的所有Symbol 属性
image.png

获取所有属性和描述

  1. console.log(Object.getOwnPropertyDescriptors(o)); //返回同时包含 常规 和 符号 属性描述符的对象
  2. // {baz: {...}, qux: {...}, Symbol(foo): {...}, Symbol(bar): {...}}

一些方法

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol

3、删除属性

  1. delete xiaoming.age;

4、in 检测是否拥有某一属性

  1. 'name' in xiaoming; // true
  2. 'grade' in xiaoming; // false

不过要小心,如果in判断一个属性存在,这个属性不一定是xiaoming的,它可能是xiaoming继承得到的:
‘toString’ in xiaoming; //true,因为toString定义在object对象中,而所有对象最终都会在原型链上指向object,所以xiaoming也拥有toString属性

推荐用hasOwnProperty()方法来判断属性是否该对象下的

  1. xiaoming.hasOwnProperty('name'); // true
  2. xiaoming.hasOwnProperty('toString'); // false

5、instanceof 检测是否对象的实例

  1. console.log(person instanceof Object); // 变量 person 是 Object 吗?
  2. console.log(colors instanceof Array); // 变量 colors 是 Array 吗?
  3. console.log(pattern instanceof RegExp); // 变量 pattern 是 RegExp 吗?

6、遍历属性

for…in

JS-07.流程控制

Object.keys

Object.values

Object.entries

image.png
会忽略Symbols 属性
image.png image.png

7、拷贝(复制属性)

【目的】:复制一个对象的所有值到另一个对象
【说明】:一看以为很简单,不就是a=b。但对象实际是引用类型,表示的是一个内存地址的值,因此a=b只是把a的内存地址赋值给了b,这样修改a的属性,同样会导致b显示的属性会变化,因为他们本质是同一个对象。

【应用场景】:如Vue组件,你需要把一个初始对象传给子组件,同时我并不想这个组件把我初始值给修改了,因此需要拷贝。

浅拷贝

只拷贝第一层属性,如果第一层有的属性是引用类型(对象,数组这些表示内存地址的),只是把内存地址复制过去

  1. // 1、const 新对象 = Object.assign({}, 旧对象);
  2. let obj1 = { a: 0 , b: { c: 0}};
  3. let obj2 = Object.assign({}, obj1);
  4. // 或者
  5. let obj3 = {...obj1}
  6. log(JSON.stringify(obj2));
  7. // { a: 0, b: { c: 0}}
  8. // 只拷贝了表面一层
  9. obj1.a = 1;
  10. log(JSON.stringify(obj1));// { a: 1, b: { c: 0}}
  11. log(JSON.stringify(obj2));// { a: 0, b: { c: 0}}
  12. // 无法拷贝深层
  13. obj1.b.c = 111;
  14. JSON.stringify(obj1)// "{"a":0,"b":{"c":111}}"
  15. JSON.stringify(obj2)// "{"a":0,"b":{"c":111}}"
  1. // 2、解构赋值拷贝
  2. let person = {
  3. name: 'Matt',
  4. age: 27,
  5. job: {
  6. title: 'Software engineer'
  7. }
  8. };
  9. let personCopy = {};
  10. ({
  11. name: personCopy.name,
  12. age: personCopy.age,
  13. job: personCopy.job
  14. } = person);
  15. // 声明 title 变量并将 person.job.title 的值赋给它
  16. let { job: { title } } = person;
  17. console.log(title); // Software engineer
  18. // 因为一个对象的引用被赋值给 personCopy,所以修改
  19. // person.job 对象的属性也会影响 personCopy
  20. person.job.title = 'Hacker'
  21. console.log(person);
  22. // { name: 'Matt', age: 27, job: { title: 'Hacker' } }
  23. console.log(personCopy);
  24. // { name: 'Matt', age: 27, job: { title: 'Hacker' } }

深拷贝

方法一:
但是null、undefined会变成文本,函数也有问题

  1. const b = {b:0}
  2. const obj = { o: 1 ,b};
  3. const copy = JSON.parse(json.stringify(obj))
  4. console.log(copy); // { o: 1,b:0 }
  5. b.b=1
  6. console.log(copy); // { o: 1,b:0 }

方法二:递归拷贝
循环判断对象的每个属性,如果是对象或者数组(引用类型),就进入继续判断,直到是数值、文本、布尔类型,然后拷贝。

相当于每个元素都完全拷贝过来

8、判断两个值是否完全相等 Object.is

image.png image.png

对象操作

对象合并 Object.assign

image.png image.png

设置原型对象

不建议直接设置原型对象,应该构造时设置上
image.png image.png
image.png

操作总结

不要使用new Number()、new Boolean()、new String()创建包装对象;

  1. 请使用对象字面量 {} 代替 new Object()。
  2. 请使用字符串字面量 "" 代替 new String()。
  3. 请使用数值字面量代替 Number()。
  4. 请使用布尔字面量代替 new Boolean()。
  5. 请使用数组字面量 [] 代替 new Array()。
  6. 请使用模式字面量代替 new RexExp()。
  7. 请使用函数表达式 () {} 代替 new Function()。
  8. 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’。