一、理解面向对象
- 面向对象程序设计以对象为核心,该方法认为程序由一系列对象组成。类是对现实世界的抽象,包括表示静态属性的数据和对数据的操作,对象是类的实例化。面向对象有三个特点:封装性、继承性、多态性。
-
封装性
- 将描述每一个对象的属性以及其行为的程序代码组装到一起,一并封装在一个有机的实体中,把它们封装在一个“模块”中,也就是一个类中。
-
继承性
- 继承性是面向对象技术中的另外一个重要特点,继承在面向对象技术是指一个对象针对于另一个对象的某些独有的特点、能力进行复制或者延续。
-
多态性
- 从宏观的角度来讲,多态性是指在面向对象技术中,当不同的多个对象同时接收到同一个完全相同的消息之后,所表现出来的动作是各不相同的,具有多种形态;从微观的角度来讲,多态性是指在一组对象的一个类中,面向对象技术可以使用相同的调用方式来对相同的函数名进行调用。
二、对象创建的方式
1.new Object() 方式
const obj = new Object()
obj.name = 'cara'
obj.age = 18
console.log(obj)
2.字面量方式
const obj = {
name: 'cara',
age: 18
}
console.log(obj)
3.工厂模式
-
工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。但是它有一个很大的问题就是创建出来的对象无法和某个类型联系起来,它只是简单的封装了复用代码,而没有建立起对象和类型间的关系。
function star (name, age) { const obj = { name: name, age: age } return obj } const ldh = star('刘德华', 18) console.log(ldh)
4.构造函数方式
-
js 中每一个函数都可以作为构造函数,只要一个函数是通过 new 来调用的,那么就可以把它称为构造函数。执行构造函数首先会创建一个空对象,然后将对象的原型指向构造函数的 prototype 属性,然后将执行上下文中的 this 指向这个对象,最后再执行整个函数,给实例对象添加属性和方法,最后再指回这个新对象。构造函数模式相对于工厂模式的优点是,所创建的对象和构造函数建立起了联系,因此可以通过原型来识别对象的类型。但是构造函数存在一个缺点就是,造成了不必要的函数对象的创建,因为在 js 中函数也是一个对象,因此如果对象属性中如果包含函数的话,那么每次都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。
function Star (name, age) { this.name = name, this.age = age, this.sing = function () { console.log(this.name) } } const ldh = new Star('刘德华', 18) ldh.sing()
5.原型方式
-
因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。因此可以使用原型对象来添加公用属性和方法,从而实现代码的复用。这种方式相对于构造函数模式来说,解决了函数对象的复用问题。但是这种模式也存在一些问题,一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如 Array 这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。
function Star () { } Star.prototype.name = 'cara' Star.prototype.age = 18 Star.prototype.sing = function () { console.log(this.name) } const ldh = new Star('刘德华', 18) ldh.sing()
6.构造函数+原型方式
-
这是创建自定义类型的最常见方式。因为构造函数模式和原型模式分开使用都存在一些问题,因此可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。这种方法很好的解决了两种模式单独使用时的缺点,但是有一点不足的就是,因为使用了两种不同的模式,所以对于代码的封装性不够好。
function Star (name, age) { this.name = name this.age = age } Star.prototype.sing = function () { // 构造函数原型身上的方法也指向实例对象 console.log(this.age) } const ldh = new Star('刘德华', 18) ldh.sing()
7.类方式
class Star {
constructor (name, age) {
this.name = name
this.age = age
}
// 类中的方法也指向实例对象
sing () {
console.log(this.name)
}
}
const ldh = new Star('刘德华', 18)
ldh.sing()
三、对象继承的方式
1.构造函数继承(call/apply)
function Animal (name) {
this.name = "wang"
}
function Cat (name, age) {
Animal.call(this)
this.age = age
}
const cat = new Cat('wang', 1)
console.log(cat)
2.prototype原型链继承
-
子类原型 = 父类实例
function Animal (name, age) { this.name = name this.age = age } Animal.prototype.dance = function () { console.log(this.name) } function Cat (name) { Animal.call(this, name) } Cat.prototype = new Animal() Cat.prototype.constructor = Cat const cat = new Cat('neal') cat.dance() // neal
3.类继承(extends+super)
class Father {
constructor (uname) {
this.uname = uname
}
// 普通函数
sing () {
console.log(123)
}
// 构造函数
dance () {
console.log(this.uname)
}
}
class Son extends Father {
constructor (uname) {
// 调用构造函数时
super(uname)
this.uname = uname
}
// 调用普通函数时
song () {
super.sing()
}
}
const son = new Son('cara')
// 儿子调用普通函数
son.song()
// 儿子调用构造函数
son.dance()
四、new的过程
使用new调用构造函数会执行如下步骤:
- 在内存中创建一个新对象。
- 这个新对象内部的
[[Prototype]]
指针被赋值为构造函数的prototype
属性。 - 构造函数内部的
this
被赋值为这个新对象(即 this 指向新对象)。 - 执行构造函数内部的代码(给新对象添加属性)。
- 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
五、总结
最后再来个总结:
- JS中的函数可以作为函数使用,也可以作为类使用。
- 作为类使用的函数实例化时需要使用
new
。 - 为了让函数具有类的功能,函数都具有
prototype
属性。 - 为了让实例化出来的对象能够访问到
prototype
上的属性和方法,实例对象的__proto__
指向了类的prototype
。所以prototype
是函数的属性,不是对象的。对象拥有的是__proto__
,是用来查找prototype
的。 -
prototype.constructor
指向的是构造函数,也就是类函数本身。改变这个指针并不能改变构造函数。 - 对象本身并没有
constructor
属性,你访问到的是原型链上的prototype.constructor
。 - 函数本身也是对象,也具有
__proto__
,他指向的是JS内置对象Function的原型Function.prototype
。所以你才能调用func.call
,func.apply
这些方法,你调用的其实是Function.prototype.call
和Function.prototype.apply
。 -
prototype
本身也是对象,所以他也有__proto__
,指向了他父级的prototype
。__proto__
和prototype
的这种链式指向构成了JS的原型链。原型链的最终指向是Object的原型。Object上面原型链是null,即Object.prototype.__proto__ === null
。 - 因为JS中所有函数的原型都是
Function.prototype
,也就是说所有函数都是Function的实例。Function本身也是可以作为函数使用的----Function(),所以他也是Function的一个实例。类似的还有Object,Array等,他们也可以作为函数使用:Object(), Array()。所以他们本身的原型也是Function.prototype
,即Object.__proto__ === Function.prototype
。换句话说,这些可以new的内置对象其实都是一个类,就像我们的Puppy类一样。 - ES6的class其实是函数类的一种语法糖,书写起来更清晰,但原理是一样的。