JS之面向对象

JavaScript的面向对象编程和大多数其他语言如Java、C的面向对象编程都不太一样。面向对象的两个基本概念是类和实例:

类:类是对象的类型模板,例如,定义Student类来表示学生,类本身是一种类型,Student表示学生类型,但不表示任何具体的某个学生;

实例:实例是根据类创建的对象,例如,根据Student类可以创建出xiaomingxiaohongxiaojun等多个实例,每个实例表示一个具体的学生,他们全都属于Student类型。

所以,类和实例是大多数面向对象编程语言的基本概念。不过,在JavaScript中,JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。js本身是没有class类型的,但是每个函数都有一个prototype属性。prototype指向一个对象,当函数作为构造函数时,prototype则起到类似class的作用。JavaScript的原型链和Java的Class区别就在,它没有“Class”的概念,所有对象都是实例,所谓继承关系不过是把一个对象的原型指向另一个对象而已。

1、工厂模式

new Object(),new后面调用函数,我们称为构造函数。 Object() 我们把它视为一个构造函数,构造函数的本质就是一个函数, 只不过构造函数的目的是为了创建新对象,为新对象进行初始化(设置对象的属性) 。使用工厂方法创建对象 ,通过该方法可以大批量的创建对象 。工厂模式解决了重复实例化的问题,但是它有许多问题,创建不同对象时属性和方法都会重复建立,消耗内存;还有函数识别问题等等。

2、构造函数

  • 定义

    构造函数就是一个普通的函数,创建方式和普通函数没有区别,  不同的是构造函数习惯上首字母大写;
    构造函数和普通函数的区别就是调用方式的不同;
    普通函数是直接调用,而构造函数需要使用new关键字来调用。

  • 执行流程

    立刻创建一个新的对象;

    将新建的对象设置为函数中this, 在构造函数中可以使用this来引用新建的对象;
    逐行执行函数中的代码;
    将新建的对象作为返回值返回。

function Box(name, age) { //构造函数模式
     this.name = name;
     this.age = age;
     this.run = function () {
         return this.name + this.age + '运行中...';
     };
}
var box1 = new Box('Lee', 100); //new Box()即可
var box2 = new Box('Jack', 200);
alert(box1.run());
alert(box1 instanceof Box); //很清晰的识别他从属于Box
  • 构造函数和普通函数的唯一区别,就是他们调用的方式不同。只不过,构造函数也是函数,必须用new 运算符来调用,否则就是普通函数。
  • this就是代表当前作用域对象的引用。如果在全局范围this 就代表window 对象,如果在构造函数体内,就代表当前的构造函数所声明的对象。

这种方法解决了函数识别问题,但消耗内存问题没有解决。同时又带来了一个新的问题,全局中的this 在对象调用的时候是Box 本身,而当作普通函数调用的时候,this 又代表window。即this作用域的问题。

3、原型

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。逻辑上可以这么理解:prototype 通过调用构造函数而创建的那个对象的原型对象。使用原型的好处可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。

function Box() {} //声明一个构造函数
    Box.prototype.name = 'Lee'; //在原型里添加属性
    Box.prototype.age = 100;
    Box.prototype.run = function () { //在原型里添加方法
         return this.name + this.age + '运行中...';
};

构造函数的声明方式和原型模式的声明方式存储情况如下:

image

所以,它解决了消耗内存问题。当然它也可以解决this作用域等问题。

我们经常把属性(一些在实例化对象时属性值改变的),定义在构造函数内;把公用的方法添加在原型上面,也就是混合方式构造对象(构造方法+原型方式):

 

var person = function(name){
   this.name = name
  };
  person.prototype.getName = function(){
     return this.name; 
  }
  var me = new person('Jack');
  me.getName(); 

下面详细介绍原型:

3.1.原型对象

  每个javascript对象都有一个原型对象,这个对象在不同的解释器下的实现不同。比如在firefox下,每个对象都有一个隐藏的__proto__属性,这个属性就是“原型对象”的引用。

3.2.原型链

  由于原型对象本身也是对象,根据上边的定义,它也有自己的原型,而它自己的原型对象又可以有自己的原型,这样就组成了一条链,这个就是原型链,JavaScritp引擎在访问对象的属性时,如果在对象本身中没有找到,则会去原型链中查找,如果找到,直接返回值,如果整个链都遍历且没有找到属性,则返回undefined.原型链一般实现为一个链表,这样就可以按照一定的顺序来查找。

1)__proto__和prototype
JS在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__的内置属性,用于指向创建它的函数对象的原型对象prototype。以上面的例子为例:

console.log(me.__proto__ === person.prototype) //true

同样,person.prototype对象也有__proto__属性,它指向创建它的函数对象(Object)的prototype

console.log(person.prototype.__proto__ === Object.prototype) //true

继续,Object.prototype对象也有__proto__属性,但它比较特殊,为null

console.log(Object.prototype.__proto__) //null

我们把这个有__proto__串起来的直到Object.prototype.__proto__为null的链叫做原型链。如下图:

2dbd5870d84a471896d69f7d1980ae63

2)constructor
  原型对象prototype中都有个预定义的constructor属性,用来引用它的函数对象。这是一种循环引用

person.prototype.constructor === person //true
Function.prototype.constructor === Function //true
Object.prototype.constructor === Object //true

4、this 

定义解析器在调用函数每次都会向函数内部传递进一个隐含的参数, 这个隐含的参数就是this,this指向的是一个对象, 这个对象我们称为函数执行的上下文对象 根据函数的调用方式的不同,this会指向不同的对象 。

  • 以函数的形式调用时,this永远都是window ;
  • 以方法的形式调用时,this就是调用方法的那个对象 ;
  • 当以构造函数的形式调用时,this就是新创建的那个对象 ,谁调用this就是谁。
function test() { 
     console.log(this);
 } 
test(); //window.test(); //上面的this是window,实际是window调用test() 
p1.sayHi(); //sayHi()中的this,是p1,此时是p1调用sayHi() 构造函数中的this,始终是new的当前对象

5、继承

继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承。而ECMAScript 只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成。

在JavaScript 里,被继承的函数称为超类型(父类,基类也行,其他语言叫法),继承的函数称为子类型(子类,派生类)

5.1.call+遍历

属性使用对象冒充(call)(实质上是改变了this指针的指向)继承基类,方法用遍历基类原型。

function A()
{
    this.abc=12;
}

A.prototype.show=function ()
{
    alert(this.abc);
};

//继承A
function B()
{
    //继承属性;this->new B()
    A.call(this);   //有参数可以传参数A.call(this,name,age)
}

//继承方法;B.prototype=A.prototype;
for(var i in A.prototype)
{
    B.prototype[i]=A.prototype[i];
}
//添加自己的方法
B.prototype.fn=function ()
{
    alert('abc');
};

var objB=new B();
var objA=new A();objB.show();

 

 

可以实现多继承。

5.2.寄生组合继承

主要是Desk.prototype = new Box(); Desk 继承了Box,通过原型,形成链条。主要通过临时中转函数和寄生函数实现。

临时中转函数:基于已有的对象创建新对象,同时还不必因此创建自定义类型。
寄生函数:目的是为了封装创建对象的过程。

 

 

//临时中转函数
function obj(o) { //o表示将要传递进入的一个对象
function F() {}   //F构造是一个临时新建的对象,用来存储传递过来的对象
F.prototype = o;  //将o对象实例赋值给F构造的原型对象
return new F();   //最后返回这个得到传递过来对象的对象实例
}
//寄生函数
function create(box, desk) {
var f = obj(box.prototype);
f.constructor = desk;  //调整原型构造指针
desk.prototype = f;
}
function Box(name) {
this.name = name;
this.arr = ['apple','pear','orange'];
}
Box.prototype.run = function () {
return this.name;
};
function Desk(name, age) {
Box.call(this, name);
this.age = age;
}
//通过寄生组合继承实现继承
create(Box, Desk); //这句话用来替代Desk.prototype = new Box();
var desk = new Desk('Lee',100);
desk.arr.push('peach');
alert(desk.arr);
alert(desk.run());

临时中转函数和寄生函数主要做的工作流程:

临时中转函数:返回的是基类的实例对象函数。
寄生函数:将返回的基类的实例对象函数的constructor指向派生类,派生类的prototype指向基类的实例对象函数(是一个函数原型),从而实现继承。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值