JavaScript面向对象
Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象。但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类)—–es6以前是这样的。所以es5只有使用函数模拟面向对象。
面向对象的三大基本特征
- 封装:就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。我们平时所用的方法和类都是一种封装,当我们在项目开发中,遇到一段功能的代码在好多地方重复使用的时候,我们可以把他单独封装成一个功能的方法,这样在我们需要使用的地方直接调用就可以了。
- 继承:继承的过程,就是从一般到特殊的过程。它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。继承在我们的项目开发中主要使用为子类继承父类。通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。
- 多态:对象的多功能,多方法,一个方法多种表现形式。
三大特征的优点
封装:封装的优势在于定义只可以在类内部进行对属性的操作,外部无法对这些属性指手画脚,要想修改,也只能通过你定义的封装方法;
继承:继承减少了代码的冗余,省略了很多重复代码,开发者可以从父类底层定义所有子类必须有的属性和方法,以达到耦合的目的;
多态:多态实现了方法的个性化,不同的子类根据具体状况可以实现不同的方法,光有父类定义的方法不够灵活,遇见特殊状况就捉襟见肘了。
创建对象的方式
1.原始模式
var person = new Object() //创建一个Object 对象
person.name = 'sun' //创建一个name 属性并赋值
person.age = 20 //创建一个age 属性并赋值
person.say = function () { //创建一个say()方法并返回值
return this.name + this.age
}
alert(person.say()) //输出属性和方法的值
这样的写法有两个缺点,一是如果想生成多个实例,写起来就非常麻烦,而且会产生大量重复代码,这是不可取的;二是实例与原型之间,没有任何办法,可以看出有没有什么联系。
为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法就是为了解决实例化对象产生大量重复的问题。
2.工厂模式
工厂,简单来说就是投入原料、加工、出厂。
通过工厂模式来生成对象,将重复的代码提取到一个函数里面,避免像第一种方式写大量重复的代码。这样我们在需要这个对象的时候,就可以简单地创建出来了。
function createPerson(name, age){
var person = new Object()
person.name = name
person.age = age
person.say = function(){
alert("姓名:"+this.name+"年龄:"+this.age)
}
//出厂
return person
}
//创建两个对象
var p1 = createPerson("zhang", 22);
var p2 = createPerson("yu", 20);
//调用对象方法
p1.say()
p2.say()
工厂模式解决了重复实例化的问题,但是它也有许多问题,创建不同对象时,其中的属性和方法都会重复建立,消耗内存;还有函数识别问题等等。
3.构造函数模式
(1)函数名和实例化构造名相同且大写(非强制,但这么写有助于区分构造函数和普通函数);
(2)通过构造函数创建对象,必须使用new 运算符。
function Person(name, age){
this.name = name
this.age = age
this.say = function(){
alert("姓名:"+this.name+"年龄:"+this.age)
}
//不需要自己return了
}
//创建两个对象
var p1 = new Person("zhang", 22)
var p2 = new Person("yu", 20)
alert(p1.say == p2.say) //false
构造函数可以创建对象执行的过程:
(1)当使用了构造函数,并且new 构造函数(),那么就后台执行了new Object();
(2)将构造函数的作用域给新对象(即new Object()创建出的对象),而函数体内的this 就代表new Object()出来的对象,而不是指向方法调用者;
(3)执行构造函数内的代码;
(4)返回新对象(后台直接返回)。
构造函数方法很好用,解决了函数识别问题,但是存在一个浪费内存的问题。每一次生成一个实例,都会生成重复的内容,每个实例对象还是有自己的一套方法。这样既不环保,也缺乏效率。
4.在原型(prototype)上进行扩展
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。逻辑上可以这么理解:prototype是通过调用构造函数而创建的那个对象的原型对象。使用原型的好处是可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。
我们经常把属性(一些在实例化对象时属性值改变的),定义在构造函数内;把公用的方法添加在原型上面,也就是混合方式构造对象(构造方法+原型方式)
function Person(name, age){
//属性:每个对象的属性各不相同
this.name = name
this.age = age
}
//在原型上添加方法,这样创建的所有对象都是用的同一套方法
Person.prototype.say= function(){
alert("姓名:"+this.name+"年龄:"+this.age)
}
//创建两个对象
var p1 = new Person("zhang", 22)
var p2 = new Person("yu", 20)
alert(p1.say == p2.say) //true
//这里为什么两个对象的方法是相等的呢,原因如下
alert(p1.say == Person.prototype.say) //true
所以,它解决了消耗内存问题。通过prototype我们还可以很方便的扩展系统对象,按照自己的需求来扩展,而且又能适用于所有地方,又不会浪费资源。
总结:
实例对象的__proto__指向,其构造函数的原型;构造函数原型的constructor指向对应的构造函数。
有时某种原因constructor指向有问题,可以通过constructor:构造函数名;重新指向。
继承
利用call()及for in继承 :
给对象的constructor.prototype添加方法属性,对象就会继承,如果要实现一个对象继承其他对象,采用如下方法:
function Person(name,age){
this.name = name
this.age = age
}
Person.prototype.say = function(){
console.log('说话')
}
function Man(name,age){
Person.call(this,name,age)
this.sex = "男"
}
for(var key in Person.prototype){
Man.prototype[key] = Person.prototype[key]
}
//添加自己的方法
Man.prototype.fn = function(){
console.log('aaa')
}
var m = new Man('s',20)
采用中介:
function ClassA(name){
this.name = name
}
ClassA.prototype.showName = function(){
console.log(this.name)
}
//中继来做准备工作
function Ready(){}
Ready.prototype = ClassA.prototype //引用
//需要来继承ClassA
function ClassB(name){
ClassA.call(this,name)
}
ClassB.prototype = new Ready()
ClassB.prototype.constructor = ClassB
var b = new ClassB('小明')
es6继承的书写方法:
class Father {
constructor(name) {
this._name = name
}
//实例方法,通过实例对象调用
getName() {
console.log(this._name)
}
// 静态方法不会被继承,并且是通过类名去调用的
static run() {
console.log("run")
}
}
class Son extends Father {
constructor(name, age) {
//实例化子类的时候把子类的数据传给父类(这里的super必须有,super里的参数是所继承的父类实例化所需要的数据)
super(name)
this._age = age
}
}
var p = new Father('人')
Father.run()//run
p.getName() //人
var m = new Son('小明',20)
m.getName() //小明
注意:继承会继承父类的实例属性和实例方法,并不会继承静态属性和静态方法,且静态方法只能通过类名去调用。
多态
方法重载:重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数。
方法重写:重写(也叫覆盖)是指在派生类中重新对基类中的虚函数(注意是虚函数)重新实现。即函数名和参数都一样,只是函数的实现体不一样。

浙公网安备 33010602011771号