本文最后更新于:8 个月前
对象Object、类Class
JavaScript(四)面向对象 一、Object属性 对象就是一组属性的无序集合(ES定义),对象的内容可以看作一组名/值对,其中值可以是数据或函数(方法)。
对象内部属性用两个中括号括起来以示区别,开发者并不能直接访问这些属性
对象内部属性分为两种类型:数据属性、访问器属性。对于每个对象的内部属性,这两种类型只能为其中一种。
1.1、数据属性
数据描述符
说明
默认值
[[Configurable]]
表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性
false
[[Enumerable]]
表示属性是否可以通过for-in循环返回
false
[[Writable]]
表示属性的值是否可以被修改
false
[[Value]]
读取和写入属性值的位置
undefined
1.2、访问器属性 不包含数据值,包含getter和setter函数,用来读取和设置属性值
存取描述符
说明
默认值
[[Configurable]]
表示属性是否可以通过delete删除并重新定义,是否可以修改它的特性,以及是否可以把它改为数据属性
false
[[Enumerable]]
表示属性是否可以通过for-in循环返回
false
[[Get]]
获取函数,在读取属性时调用。默认值为undefined。
undefined
[[Set]]
设置函数,在写入属性时调用。默认值为undefined
undefined
修改、获取属性的特性的方法:
Object.defineProperty()方法
Object.defineProperties()方法
Object.getOwnPropertyDescriptor()方法
Object.getOwnPropertyDescriptors()方法
1.3、defineProperty()方法 ES5 提供了 Object.defineProperty 方法,该方法可以在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
defineProperty方法只能设置set和get方法
1 2 3 4 5 6 Object .defineProperty (obj, prop, descriptor)var obj = Object .defineProperty ({}, "num" , {});
1 2 3 4 5 6 7 8 var obj = {};Object .defineProperty (obj, "num" , { value : 1 , writable : true , enumerable : true , configurable : true });
1 2 3 4 5 6 7 8 obj.x = 1 ;Object .defineProperty (obj, "x" , { value : 1 , writable : true , enumerable : true , configurable : true });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const object1 = {property1 :40 };Object .defineProperty (object1, 'property2' , { get ( ){return 40 ;}, set (newVal ){console .log ("set property with ${newVal}" )} }); object1.property2 = 77 ;console .log (object1);
1.4、assign()方法 Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
1 2 3 4 Object .assign (target, ...sources)
使用场景:合并对象 。返回合并后的对象,并且将合并结果同步修改到作为第一个参数的对象,但不影响后面参数的对象
1 2 3 4 5 6 7 8 const o1 = { a : 1 };const o2 = { b : 2 };const o3 = { c : 3 };const obj = Object .assign (o1, o2, o3);console .log (obj); console .log (o1);
合并具有相同属性的对象 ,遇到同名属性,后面参数的对象中的同名属性覆盖前面的
1 2 3 4 5 6 const o1 = { a : 1 , b : 1 , c : 1 };const o2 = { b : 2 , c : 2 };const o3 = { c : 3 };const obj = Object .assign ({}, o1, o2, o3);console .log (obj);
拷贝对象(浅拷贝)
1 2 3 4 5 6 7 8 const object1 = { a : 1 , b : 2 , c : 3 };const object2 = Object .assign ({c : 4 , d : 5 }, object1);console .log (object1) console .log (object2)
二、创建对象 2.1、对象字面量表示法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 let person1 ={ name :"Liu" , age :21 }let person2 = { "first name" :"Qiu" , age :24 } person2 let person3 = { first name : 'Q' }
显而易见,这种创建对象的方式,会出现大量重复的代码,于是有了下面的构造函数模式
2.2、构造函数模式 1 2 3 4 5 6 7 function Person (name,age ){ this .name =name; this .age =age; this .sayName = function ( ){console .log (this .name );} }let person1 = new Person ('Liu' ,21 );let person2 = new Person ('Qiu' ,24 );
1 2 3 4 5 6 7 8 let Person = function (name,age ){ this .name =name; this .age =age; this .sayName = function ( ){console .log (this .name );} }let person1 = new Person ('Liu' ,21 );let person2 = new Person ;
按照惯例,构造函数名称的首字母都是要大写的 ,非构造函数则以小写字母开头
new+构造函数 底层解析 new操作符 + 构造函数 方式的创建过程,后台会执行以下步骤: (Prototype:原型)
(1)在内存中创建一个新对象。(2)这个新对象内部的[[Prototype]]特性被赋值为构造函数的prototype属性。(3)构造函数内部的this被赋值为这个新对象(即this指向新对象)。(4)执行构造函数内部的代码(给新对象添加属性)。
这种方式没有完全解决重复的问题,例如sayName函数其实代码是一样的,但是它会在每个对象中新建一个Function对象,为了解决这个问题,就引出了下面的原型模式
三、读写对象 3.1、读写对象属性 读写对象属性有两种方式:
点语法
中括号,可以使用一些不符合变量命名要求的键
1 2 person.name = "Liu" ; person["first name" ]="Liu" ;
3.2、判断对象属性存在与否
in判断。in操作符可以检查对象是否具有某个属性(实例上以及原型上)
hasOwnProperty()方法。可以检查对象实例上是否具有某个属性
1 2 'name' in person person.hasOwnProperty ('name' )
2.3、对象语法(糖) ES6为定义和操作对象新增了很多语法糖
2.3.1 属性值简写 1 2 3 4 5 let name = "Mike" ;let person = { name }console .log (person);
2.3.2 可计算属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const nameKey = 'name' ;let person = { [nameKey]:"Liu" }console .log (person); const nameKey = 'name' ;const ageKey = 'age' ;let uniqueToken = 0 ;function getUniqueKey (key ){ return `${key} _${uniqueToken++} ` ; }let person = { [getUniqueKey (nameKey)]:'Qiu' , [getUniqueKey (ageKey)]:25 }console .log (person);
2.3.3 简写方法名 1 2 3 let person = { sayHello (name ){console .log (`hello ${name} ` );} }
2.3.4 解构赋值 1 2 3 4 5 6 7 8 9 let person = { name :'Matt' , age :21 }let {name :personName,age=11 ,job="student" ,grade} = person;console .log (personName); console .log (age); console .log (job); console .log (grade);
解构并不要求变量必须在解构表达式中声明。不过,如果是给事先声明的变量赋值,则赋值表达式必须包含在一对括号中:
1 2 3 4 5 6 let personName,personAge;let person = { name :'Matt' , age :21 } ({name :personName,age=personAge} = person);
2.4、遍历对象的内容 2.4.1 for-in循环 需要注意:在for-in
循环遍历对象属性时,需要用括号标记法来访问属性值 ,而不能使用点语法
1 2 3 4 5 myObj = { "name" :"Bill Gates" , "age" :62 , "car" :null };for (x in myObj) { console .log (myObj[x]); }
直接for-in
循环会将对象原型链上的属性也遍历出来,如果要避免,可以在循环内部使用hasOwnProperty()
方法进行判断
1 2 3 4 5 for (x in obj){ if (obj.hasOwnProperty (x)){ console .log (x) } }
2.4.2 keys() Object.keys()方法可以罗列出对象所有可枚举的属性,
Object.getOwnPropertyNames()方法可以罗列出对象所有属性,不管是否可以枚举
2.4.3 values() Object.values()返回对象值的数组
2.4.4 entries() Object.entries()返回键/值对的数组
2.5、定义对象方法 在一个对象中绑定函数,称为这个对象的方法
2.5.1 对象内定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var xiaoming = { name :"小明" , birth :1998 , age :function ( ){ var y = new Date ().getFullYear (); return y-this .birth ; } } xiaoming.age (); var xiaoming = { birth :1998 , age ( ){ var y = new Date ().getFullYear () return y-this .birth } } xiaoming.age ()
2.5.2 对象外定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function getAge ( ){ var y = new Date ().getFullYear (); return y-this .birth ; }var xiaoming = { name :"小明" , birth :1998 , age :getAge } xiaoming.age (); getAge (); var fn = xiaoming.age ;fn ();
也可以使用apply或call或bind改变函数的this指向
1 2 3 4 5 6 7 8 9 function getAge ( ) { var y = new Date ().getFullYear (); return y - this .birth ; }var xiaoming = { name : '小明' , birth : 1998 }; getAge.apply (xiaoming);
2.5.3 对象方法内再定义函数 这是一种特殊情况,内部函数的this并不指向对象,而是指向全局对象window
1 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 var xiaoming = { name :"小明" , birth :1998 , age :function ( ){ function getAge ( ){ var y = new Date ().getFullYear (); console .log (this ) return y-this .birth ; } return getAge (); } } xiaoming.age () 正确的写法:var xiaoming = { name :"小明" , birth :1998 , age :function ( ){ var that = this ; function getAge ( ){ var y = new Date ().getFullYear (); return y-that.birth ; } return getAge (); } } xiaoming.age ()
四、对象原型 转到:《JS进阶(一)原型链》
五、类 在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。class 的本质是 function 。它可以看作一个语法糖 ,让对象原型的写法更加清晰、更像面向对象编程的语法。
类的所有方法都定义在类的prototype属性上面,在类的实例上面调用方法,其实就是调用原型上的方法 原型方法可以通过实例对象调用,但不能通过类名调用,会报错
5.1、创建类 5.1.1 类定义 把类表达式赋予一个变量,存在类定义的作用之一,是可以写出立即执行的Class
1 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 let Example = class { constructor (a ){ this .a =a } }let e = new Example (1 )console .log (e) let Example = class E { constructor (a ){ this .a =a } }let e1 = new Example (2 ) let e2 = new E (3 ) let person = new class { constructor (name ) { this .name = this .name ; } sayname ( ){ console .log (this .name ); } }("常东东" )
5.1.2 类的声明 1 2 3 4 5 class Person {} const Animal = class {} var person1 = new Person () var person2 = new Person
函数声明可以提升,但类定义不能
函数受函数作用域限制,而类受块作用域限制
5.1.3 添加类实例成员 在构造函数内,为实例添加“自有”属性,在构造函数执行后,还可以在外部为实例添加成员
注意,构造函数中的内部变量需要加 this. 前缀。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Person { constructor ( ){ this .name ='Mike' ; this .sayName = ()=> console .log (this .name ); } }let p1 = new Person ();class Person { constructor (a=1 ){ this .a = a } }
5.1.4 添加类方法 类中方法不用加 function 关键字
定义在constructor 内的属性和方法 即调用在this上 属于实例属性和方法 否则属于原型属性和方法
1 2 3 4 5 6 7 8 9 10 11 class Person { constructor ( ){ this .name ='Mike' ; this .sayName = ()=> console .log (this .name ); } locate ( ){ console .log ('prototype' ); } }
5.1.5 静态方法 不需要通过实例对象,可以直接通过类来调用的方法,其中的 this 指向类本身。实例中不会出现这个静态方法,也无法调用。静态方法可以被子类继承
1 2 3 4 5 6 7 8 9 class Person { static say ( ){console .log ('hello' )} }Person .say () let p = new Person console .log (p) p.say () class Child extends Person {}Child .say ()
5.1.6 类访问器 语法与行为跟普通对象一样
1 2 3 4 5 6 7 class Person { set name (newName ){this .name_ = newName;} get name (){return this .name_ ;} }let p = new Person (); p.name ="Jake" ;console .log (p.name );
5.2、类的继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Student { constructor (name ){ this .name = name; } hello ( ){ return "Hello " + this .name ; } }var Liu = new Student ('Liu' );class PrimaryStudent extends Student { constructor (name,grade ){ super (name); this .grade = grade; } getGrade ( ){ return this .grade ; } }
ECMAScript 6新增特性中最出色的一个就是原生支持了类继承机制。虽然类继承使用的是新语法,但背后依旧使用的是原型链
通过 extends 实现类的继承。如果子类定义了 constructor,必须在它里面调用 super 方法
5.2.1 extends关键字 extends关键字用来实现类的继承,如下
1 2 3 4 class Vehicle {}class Bus extends Vehicle {}let b = new Bus (); b instanceof Vehicle
派生类都会通过原型链访问到类和原型上定义的方法。this的值会反映调用相应方法的实例或者类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Vehicle { identifyPrototype (id ){ console .log (id,this ); } static identifyClass (id ){ console .log (id,this ); } }class Bus extends Vehicle {}let v = new Vehicle ();let b = new Bus ; v.identifyPrototype ('vehicle' ); b.identifyPrototype ('bus' ); Bus .identifyClass ('bus' );Vehicle .identifyClass ('vehicle' );
5.2.2 super()方法 super关键字用于访问和调用 父类上的函数,可以调用父类的构造函数 也可以调用父类的普通函数
如果在子类中定义了constructor方法,必须在里面调用super()
1 2 3 4 5 6 7 8 9 10 class Vehicle { constructor ( ){ this .hasEngine =true ; } }class Bus { constructor ( ){ super (); } }
1 2 3 4 5 6 7 8 9 10 11 12 class Vehicle { static identify ( ){ console .log ('vehicle' ); } }class Bus extends Vehicle { static test ( ){ super .identify (); } }Bus .test ();
1 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 31 32 33 34 35 36 class Father { constructor (surname ){this .surname = surname} say ( ){console .log ('父级:' +this .surname )} }class Child extends Father { constructor (surname,name ){ super (surname) this .name = name } say ( ){ super .say () console .log ('子级:' +this .name ) } }let p = new Child ('华师' ,'计算机' ) p.say () class Father { constructor (surname ){this .name = surname} say ( ){console .log ('父级:' +this .name )} }class Child extends Father { constructor (surname,name ){ super (surname) this .name = name } say ( ){ super .say () console .log ('子级:' +this .name ) } }let p = new Child ('华师' ,'计算机' ) p.say ()
5.3 判断对象属于哪个类 1 2 3 4 5 6 7 class Example {}let e = new Example e instanceof Example e.constructor === Example Example .prototype .isPrototypeOf (e) Object .getPrototypeOf (e) === Example .prototype
参考文章 es6之(class)类的用法 - 掘金 (juejin.cn)
[4.3 ES6 Class 类 | 菜鸟教程 (runoob.com)](https://www.runoob.com/w3cnote/es6-class.html#:~:text=在ES6中,class (类)作为对象的模板被引入,可以通过,class 关键字定义类。)