一、对象的创建
1. 对象(字面量)
-
JS对象是一系列键值对的集合,值可以是基础类型也可以是函数或嵌套的对象,键和值用
:
连接,键值对之间用逗号,
分隔。let obj={ a:"alice", //值为字符串 b:function(){ //值为函数 ,使用ES6字面量增强后这行可以这么写 `b(){` console.log(this.a) }, c:{ //值为对象 d:1 } }
2. 构造函数
-
用于创建对象的函数。
-
首字母大写来表示其是一个构造函数。
-
使用
new
关键字来通过构造函数创建对象。 -
函数定义里使用的是等号
=
和;
,而对象定义里使用的是:
和,
。 -
相比字面量的优势是可以复用以及传递参数
function Func(){ //表达式形式 `const Func=function(){` ,不能使用箭头函数,因为箭头函数没有自己的this绑定 this.a = 'alice'; this.b = function(){ console.log(this.a); }; this.c = {d:1} } let obj = new Func()
3. 工厂函数
- 工厂函数将需要构造的对象通过
return
返回,就不需要new
关键字了,也不需要this
,是一种结合构造函数和字面量的写法,function Func(a){ return { a, //ES6的字面量增强写法,相当于`a:a` b(){ //ES6的字面量增强写法,相当于`b:function(){` console.log(this.a); }, c :{d:1}} } let obj=Func('alice')
4. class
(ES6)
- 类是用于创建对象(实例)的模板(通过
new
关键字创建)。 - JS 中的类建立在原型之上。
- 类实际上是“特殊的函数”。
- 类的主体会执行在
严格模式
下。 extend
派生,super
继承- 类的三个关键特征:
- 构造函数
constructor()
- 实例方法和实例字段
- 静态方法和静态字段
- 构造函数
- 类元素的三个特征:
- 种类:
getter
、setter
、方法、字段 - 位置:静态的或位于实例上
- 可见性:公有或私有
- 种类:
- 静态:拥有
static
前缀,是一组在类本身上定义的特性,而不是在类的实例上定义的特性,不能从实例中访问。 - 私有:私有字段是以
#
(井号)开头的标识符。在类外访问私有字段会导致语法错误,在浏览器控制台中运行的代码可以在类外访问私有字段,JavaScript 为了方便调试而仅在 DevTools 中放宽了这一限制。class Func{ constructor(a,c){ this.a=a; this.c=c; this.b=function(){ console.log(this.a); } } e(){ console.log(this.a); } static f(){ console.log(this.a); } #g(){ console.log(this.a); } } const obj=new Func('alice',{d:1})
b()
、e()
、f()
和g()
的区别b()
可以通过实例进行访问,且会存在于创建的实例对象中。e()
可以通过实例进行访问,但不会存在于创建的实例对象中,而是存在于其原型(prototype
)属性中。f()
无法通过实例进行访问,但可以通过Func.f()
进行访问,需要注意的是此时Func.f()
不会打印'alice'
,而是会打印undefined
,原因是因为this
此时指向Func
,而Func
自身并没有a
的属性,如果想正常输出,那么需要定义一个静态static
的a
属性,如果没有带static
关键字,那么这个这个属性将作为实例属性出现在创建的实例对象中。g()
无法通过实例进行访问,无法在类外访问。
5. Object.create()
(原型式继承)
Object.create(proto)
以一个现有对象为原型,创建一个新对象。
6. Object.assign()
Object.assign(target, ...sources)
将一个或者多个源对象中所有可枚举的自有属性复制到目标对象(即只执行浅拷贝,嵌套对象不会被深拷贝。),并返回修改后的目标对象。target
也会被修改,其值就是返回值。
二、对象的继承
1. 原型链继承
- 利用原型对象来实现继承,在JS中,每个对象都有一个原型对象。当访问一个对象的属性或方法时,如果该对象本身不存在这个属性或方法,就会在他的原型对象中查找,这样就形成了原型链。
__proto__
和prototype
- 每个对象都有一个隐藏的内部属性
[[Prototype]]
,而__proto__
是访问或设置这个内部属性的公共接口。 prototype
是函数(包含构造函数)的一个属性,指向一个对象,该对象会成为由该函数创建的所有实例的原型。
- 每个对象都有一个隐藏的内部属性
- 优点和缺点:
- 优点:简单,父类方法可以被多个子类共享,减少了代码冗余。
- 缺点:所有子类对象共享父类的属性,相互影响。
2. 原型式继承 Object.create()
- 以一个现有对象为原型,创建一个新对象。
3. 构造函数继承 (经典继承)
-
在子类的构造函数中通过
call()
或apply()
方法调用父类的构造函数,将父类的属性复制到子类的对象中。// 定义父类构造函数 function Parent(name) { this.name = name; this.sayHello = function() { console.log('Hello from ' + this.name); }; } // 定义子类构造函数 function Child(name) { Parent.call(this, name); } let child = new Child('child'); child.sayHello();
-
优点和缺点
- 优点:可向构造函数传参,且每个子类对象属性相互独立。
- 缺点:构造函数中的方法在每个对象都要重新创建,无法复用,浪费空间,且无法继承父类的原型方法。
4. 组合继承(原型链继承和构造函数继承的结合)
- 属性通过构造函数继承
- 方法通过原型链继承
- 优点及缺点
- 优点:结合了原型链继承和构造函数继承的优点
- 缺点:在实现过程调用了两次构造函数,影响性能
5.寄生式继承
- 在原型式继承的基础上,在函数内部增强对象,返回这个增强的对象。
- 优点和缺点:
- 优点:可以在继承的基础上灵活添加新的属性和方法,对已有对象进行增强。
- 缺点:对象间共享原型属性。复用性不高。
6. 寄生组合继承
-
解决了组合继承中父类构造函数被调用两次的问题。通过
Object.create()
方法来继承父类的原型,避免了子类原型上再次调用父类构造函数。function Parent(name) { this.name = name; } Parent.prototype.sayHello = function() { console.log('Hello from ' + this.name); }; function Child(name) { Parent.call(this, name); } let F = Object.create(Parent.prototype); F.constructor = Child; Child.prototype = F; let child = new Child('child'); child.sayHello();
-
优点和缺点:
- 优点:综合了组合继承的优点,且性能更好。是一种比较理想的继承方式。
- 缺点:实现过程相对复杂,理解起来有些难度。
7. class继承(ES6)
-
使用
extends
实现类的继承。class Parent { greet() { console.log("Hello!"); } } class Child extends Parent { sayHi() { console.log("Hi!"); } } const child = new Child(); child.greet(); // "Hello!"