关于javascript面向对象和原型

 ECMAScript有两种开发模式:1.函数式(过程化),2.面向对象(OOP)。面向对象的语言有一个标志,那就是类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。但是,ECMAScript没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。

二.创建对象
创建一个对象,然后给这个对象新建属性和方法。
var box = new Object();                 //创建一个Object对象
box.name = 'Lee';                       //创建一个name属性并赋值
box.age = 100;                          //创建一个age属性并赋值
box.run = function () {                 //创建一个run()方法并返回值
  return this.name + this.age + '运行中...';
};
alert(box.run());                       //输出属性和方法的值
以上这种方式,是创建单个对象的方式.如果,我们要是想创建多个对象,那么我们就必须用到函数传递参数的方式.
为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法就是为了解决实例化对象产生大量重复的问题。
function create(name,age){
            var obj = new Object()
            obj.name = name
            obj.age = age
            obj.run = function(){
                alert(this.name+this.age)
            }
            return obj
        }
        var box = create("咖啡",255)
        box.run()//输出咖啡255
工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法搞清楚他们到底是哪个对象的实例。
alert(typeof box1);                     //Object
alert(box instanceof create)          //false

ECMAScript中可以采用构造函数(构造方法)可用来创建特定的对象。类型于Object对象。

function Create(name,age){
            this.name = name
            this.age = age
            this.run = function(){
                alert(this.name+this.age)
            }
        }
        var box = new Create("咖啡",30)
        box.run()//输出咖啡30
        alert(box instanceof Create)     //true

使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题,但问题是,这里并没有new Object(),为什么可以实例化Box(),这个是哪里来的呢?
使用了构造函数的方法,和使用工厂模式的方法他们不同之处如下:
1.构造函数方法没有显示的创建对象(new Object());
2.直接将属性和方法赋值给this对象;
3.没有renturn语句。

构造函数的方法有一些规范:
1.函数名和实例化构造名相同且大写,(PS:非强制,但这么写有助于区分构造函数和普通函数);
2.通过构造函数创建对象,必须使用new运算符。

既然通过构造函数可以创建对象,那么这个对象是哪里来的,new Object()在什么地方执行了?执行的过程如下:
1.当使用了构造函数,并且new 构造函数(),那么就后台执行了new Object();
2.将构造函数的作用域给新对象,(即new Object()创建出的对象),而函数体内的this就代表new Object()出来的对象。
3.执行构造函数内的代码;
4.返回新对象(后台直接返回)。

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

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

比较一下原型内的方法地址是否一致:
var box1 = new Box();
var box2 = new Box();
alert(box1.run == box2.run);                    //true,方法的引用地址保持一致

为了让属性和方法更好的体现封装的效果,并且减少不必要的输入,原型的创建可以使用字面量的方式:
function Box() {};
Box.prototype = {                       //使用字面量的方式
    name : 'Lee',
    age : 100,
    run : function () {
        return this.name + this.age + '运行中...';
    }
};

使用构造函数创建原型对象和使用字面量创建对象在使用上基本相同,但还是有一些区别,字面量创建的方式使用constructor属性不会指向实例,而会指向Object,构造函数创建的方式则相反。
var box = new Box();
alert(box instanceof Box);
alert(box instanceof Object);
alert(box.constructor == Box);              //字面量方式,返回false,否则,true
alert(box.constructor == Object);           //字面量方式,返回true,否则,false

但利用字面量的形式,我们就会发现,它的构造函数就不是Box了,它的结果返回的是一个false

PS:字面量方式为什么constructor会指向Object?因为Box.prototype={};这种写法其实就是创建了一个新对象。而每创建一个函数,就会同时创建它prototype,这个对象也会自动获取constructor属性。所以,新对象的constructor重写了Box原来的constructor,因此会指向新对象,那个新对象没有指定构造函数,那么就默认为Object。

原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型。
function Box() {};

Box.prototype = {                           //原型被重写了
    constructor : Box,
    name : 'Lee',
    age : 100,
    run : function () {
        return this.name + this.age + '运行中...';
    }
};

Box.prototype = {
    age = 200
};

var box = new Box();                        //在这里声明
alert(box.run());                           
后写的原型会覆盖掉原来的原型,另外如果是字面量的形式存在name属性,构造函数也存在name的话,那么就会是字面量形式的原型优于构造函数的属性
function Box(){
            name:"绿茶"
        }
        Box.prototype = {

            name:"咖啡1",
            age:25,
            run:function(){
                alert(this.name+this.age)
            }
        }



        var box = new Box()
        alert(box.name) //咖啡1

原型对象不仅仅可以在自定义对象的情况下使用,而ECMAScript内置的引用类型都可以使用这种方式,并且内置的引用类型本身也使用了原型。
alert(Array.prototype.sort);                    //sort就是Array类型的原型方法
alert(String.prototype.substring);              //substring就是String类型的原型方法

String.prototype.addstring = function () {      //给String类型添加一个方法
    return this + ',被添加了!';             //this代表调用的字符串
};

alert('Lee'.addstring());                       //使用这个方法

原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。
原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题:

function Box() {};
Box.prototype = {
    constructor : Box,
    name : 'Lee',
    age : 100,
    family : ['父亲', '母亲', '妹妹'],            //添加了一个数组属性
    run : function () {
        return this.name + this.age + this.family;
    }
};

var box1 = new Box();
box1.family.push('哥哥');                 //在实例中添加'哥哥'
alert(box1.run());

var box2 = new Box();
alert(box2.run());                          //共享带来的麻烦,也有'哥哥'了

PS:数据共享的缘故,导致很多开发者放弃使用原型,因为每次实例化出的数据需要保留自己的特性,而不能共享。

为了解决构造传参和共享问题,可以组合构造函数+原型模式:
function Box(name, age) {                   //不共享的使用构造函数
    this.name = name;
    this.age = age;
    this. family = ['父亲', '母亲', '妹妹'];
};
Box.prototype = {                           //共享的使用原型模式
    constructor : Box,
    run : function () {
        return this.name + this.age + this.family;
    }
};

猜你喜欢

转载自blog.csdn.net/only_ruiwen/article/details/80876595