JS之面向对象和原型

作者: fenerchen | 来源:发表于2018-03-14 09:44 被阅读25次

    创建对象

    1、 工厂模式

    若使不同的对象拥有相同的属性和方法,如果每个对象都单独定义一遍,会产生大量代码。工厂模式,就是解决这个问题,而且,每次调用返回的对象相互独立。工厂模式把创建对象封装在一个函数里来实现。

    function createPerson(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
    alert(this.name);
    };
    return o;
    }
    var person1 = createPerson("Nicholas", 29, "Software Engineer");
    person1.sayName()//"Nicholas"
    var person2 = createPerson("Greg", 27, "Doctor");
    person2.sayName()//"Greg"
    

    千万不要对对象直接复制,下面这样做,box1和box指向同一个对象,其一变,则都变

    var box = new Object();
    box.age = 100; //创建一个age 属性并赋值
    box.run = function () { //创建一个run()方法并返回值
    return this.name + this.age + '运行中...';
    };
    var box1=box;
    box1.age=1;
    console.log(box.age)//1
    

    工厂模式
    优点:解决代码重复(重复化实例)
    缺点:无法区分是哪个对象的实例

    2、构造函数

    与工厂模式的不同:
    1.构造函数方法没有显示的创建对象(new Object());
    2.直接将属性和方法赋值给this 对象;
    3.没有renturn 语句。

    function Person(name){
        this.name=name;
        this.sayName=function(){
            console.log(this.name);
        }
    }
    var p1=new Person('A');
    p1.sayName();//A
    

    p1有一个constructor属性,指向Person

    console.log(p1.constructor==Person);//true
    

    对象的constructor属性最初是用来标识对象类型的,但是检测对象类型建议使用instanceof。

    console.log(p1 instanceof Person);//true
    console.log(p1 instanceof Object);//true
    

    构造函数的规范:1、函数名和实例化构造名相同且大写(非强制)2、通过构造函数创建对象,必须用new运算符
    构造函数执行过程:
    1、当使用了构造函数,并且new构造函数(),那么后台就执行了new Object();
    2、将构造函数的作用域给新对象,而函数体内的this就代表new Object()出来的对象。
    3、执行构造函数内的代码
    4、返回新对象

    构造函数优点;解决代码重复,解决对象类别识别问题
    缺点:每个方法都要在实例上创造一遍,因为函数也是对象,每定义一个函数,就实例化了一个对象new Function().因此
    console.log(p1.sayName==p2.sayName);//false
    解决:把函数定义转移到外部,p1,p2共享全局下的同一个sayName()。但是,这样做毫无封装可言

    function Person(name){
        this.name=name;
        this.sayName=sayName;
    }
    function sayName(){
            console.log(this.name);
        }
    var p1=new Person('A');
    var p2=new Person('b');
    console.log(p1.sayName==p2.sayName);//true
    

    3、原型(函数中的一个对象属性)

    每个函数有一个prototype属性,这个属性指向函数的原型对象,该属性包含由特定类型的所有实例共享的属性和方法。不妨理解为,prototype是通过调用构造函数而创建的那个实例对象的原型对象。不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。

    function Person(){
    }
    Person.prototype.name = "Nicholas";
    Person.prototype.age = 29;
    Person.prototype.job = "Software Engineer";
    Person.prototype.sayName = function(){
    alert(this.name);
    };
    var person1 = new Person();
    person1.sayName(); //"Nicholas"
    var person2 = new Person();
    person2.sayName(); //"Nicholas"
    alert(person1.sayName == person2.sayName); //true
    

    [[Prototype]]在创建实例时,自动生成,他是一个指针,指向构造函数的原型对象,实例与构造函数没有直接关系。通过isPrototypeOf()方法来确定对象之间是否存在这种关系。实例person1/2和原型的constructor均指向构造函数
    console.log(Person.prototype.isPrototypeOf(person1))//true
    

    另一个是Object.getPrototypeOf()获取对象的原型

    console.log(Object.getPrototypeOf(person1)==Person.prototype)//true
    

    原型模式的执行流程:
    1.先查找构造函数实例里的属性或方法,如果有,立刻返回;
    2.如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;
    使用hasOwnProperty()方法来检测一个属性是存在于实例中还是原型中,

    console.log(person1.hasOwnProperty('name'))//false
    person1.name='w';
    console.log(person1.hasOwnProperty('name'))//true
    console.log(person1.name)//w
    

    in操作符会在通过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原型中.

    console.log(person1.hasOwnProperty('name'))//false
    console.log('name' in person1);//true
    person1.name='w';
    console.log(person1.hasOwnProperty('name'))//true
    console.log('name' in person1);//true
    

    为了让属性和方法更好的封装,可用字面量方式,但是要注意,字面量创建的方式使用constructor属性不会指向构造函数,而是指向Object

    function Person(){
    }
    Person.prototype={
        name:'s',
        sayName:function(){
            console.log(this.name);
    
        }
    }
    var p1=new Person();
    console.log(p1.constructor==Person);//false
    console.log(p1.constructor==Object);//true
    

    字面量方式,需要强制constructor指向构造函数

    function Person(){
    }
    Person.prototype={
       constructor:Person,
        name:'s',
        sayName:function(){
            console.log(this.name);
    
        }
    }
    var p1=new Person();
    console.log(p1.constructor==Person);//true
    console.log(p1.constructor==Object);//false
    

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

    原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型。

    function Person(){
    }
    Person.prototype={
        constructor:Person,
        name:'s',
        sayName:function(){
            console.log(this.name);
    
        }
    }
    var p1=new Person();
    Person.prototype={
        constructor:Person,
        name:'h',
        sayName:function(){
            console.log(this.name);
    
        }
    }
    var p2=new Person();
    p2.sayName()//h
    p1.sayName()//s
    

    原型优缺点:
    它省略了构造函数传参初始化这一过程,带来的缺
    点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。

    原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题,一个实例改动,其他实例的该属性也跟着变了
    解决:组合构造函数+原型模式

    function Person(){
     this.name=['s','a'];
    }
    Person.prototype={
        constructor:Person,
        sayName:function(){
            console.log(this.name);
    
        }
    }
    var p1=new Person();
    p1.name.push('d')
    var p2=new Person();
    p2.sayName()//['s','a'];p2的name没有随着改变
    p1.sayName()// ["s", "a", "d"]
    

    寄生构造函数,其实就是工厂模式+构造函数模式。这种模式比较通用,但不能确定对象关系,所以,在可以使用之前所说的模式时,不建议使用此模式。
    在什么情况下使用寄生构造函数比较合适呢?假设要创建一个具有额外方法的引用类型。由于之前说明不建议直接String.prototype.addstring,可以通过寄生构造的方式添加。给js自带对象添加属性和方法

    function myString(string) {
    var str = new String(string);
    str.addstring = function () {
    return this + ',被添加了!';
    };
    return str;
    }
    var box = new myString('Lee'); //比直接在引用原型添加要繁琐好多
    alert(box.addstring());
    

    在一些安全的环境中,比如禁止使用this 和new,这里的this 是构造函数里不使用this,这里的new 是在外部实例化构造函数时不使用new。这种创建方式叫做稳妥构造函数。所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this 的对象。

    function Box(name , age) {
    var obj = new Object();
    obj.run = function () {
    return name + age + '运行中...'; //直接打印参数即可
    };
    return obj;
    }
    var box = Box('Lee', 100); //直接调用函数
    alert(box.run());
    //稳妥构造函数和寄生类似。
    

    继承

    利用原型链实现继承,所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。大家要记住,所有函数的默认原型都是Object 的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。

    function SuperType(){
    this.property = true;
    }
    SuperType.prototype.getSuperValue = function(){
    return this.property;
    };
    function SubType(){
    this.subproperty = false;
    }
    //继承了SuperType
    SubType.prototype = new SuperType();
    SubType.prototype.getSubValue = function (){
    return this.subproperty;
    };
    var instance = new SubType();
    alert(instance.getSuperValue()); //true
    

    构造函数A,在构造函数A的原型中定义一些属性和方法。在定义一个构造函数B,让B的原型称为A的一个实例,那么B原型会指向A原型,B的实例会继承A原型中的属性和方法,。且指向B原型

    构造函数、原型和实例的关系:每个构造函数(prototype属性指向原型函数)都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。

    记住一点,实例指向构造函数的原型,实例和构造函数之间没有直接关系。

    参考资料:JavaScript高级程序设计(第3版)

    相关文章

      网友评论

        本文标题:JS之面向对象和原型

        本文链接:https://www.haomeiwen.com/subject/aynofftx.html