资讯专栏INFORMATION COLUMN

基础二:javascript面向对象、创建对象、原型和继承总结(上)

fevin / 3329人阅读

摘要:创建对象两个基本方法创建对象最基本的两个方法是构造函数和对象字面量。当调用构造函数创建一个新的实例对象后,该实例内部会有一个指针指向构造函数的原型对象。码农构造函数在不返回值的情况下,默认会返回新对象实例。

前言:本文主要总结一下javascript创建对象的方法、原型、原型链和继承,但是先从创建对象的几种方法开始,延伸到原型模式创建对象以及其它模式。继承本来想一块写了,发现太多内容了,放到下里总结。

1.创建对象 (1)两个基本方法

创建对象最基本的两个方法是:Object构造函数对象字面量

    //Object构造函数方式
    var person = new Object();
    person.name = "Jack";
    person.age = 12;
    person.sayName = function(){
        alert(this.name);
    };
    
    //字面量方式
    var person = {
        name: "Jack",
        age: 14,
        job: "码农",
        sayName: function(){
            alert(this.name);
        }
    };
(2)工厂模式

上述两个基本方法的缺点是:使用同一个接口创建很多对象,会产生大量的复制代码。针对这个缺点,看下面
原理是用函数来封装以特定接口创建对象的细节

    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("Jack",15,"码农");
    var person2 = createPerson("rose",12,"程序媛");

函数createPerson能接收参数构建一个包含所有属性的对象,并且可以用很少的代码不断的创建多个对象,但是由于它被函数所封装,暴露的接口不能有效的识别对象的类型(即你不知道是Object还是自定义的什么对象)。

(3)构造函数模式
    function Person(name,age,job){
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function(){
            alert(this.name);
        };
    }
    var person1 = new Person("Jack",15,"码农");  //满满的java复古风
    var person2 = new Person("Rose",15,"程序媛");

与工厂模式相比,构造函数模式用Person()函数代替了createPerson()函数,并且没有显示的创建对象,直接把属性和方法赋值给了this对象。

要创建Person的实例,必须使用new关键字。

person1和person2都是Person的实例,这两个对象都有一个constructor(构造函数)属性,该属性指向Person。 person1.constructor == Person; //true

person1即是Person的实例又是Object的实例,后面继承原型链会总结。

(3).1构造函数的使用
     //当做构造函数使用
     var person1 = new Person("Jack",15,"码农");
     person1.sayName();   //"Jack"
     
     //当做普通函数使用
     Person("Jack",16,"码农");     //注意:此处添加到了window
     window.sayName();  //"Jack"
     
     //在另一个对象的作用域中调用
     var o = new Object();
     Person.call(o,"Jack",12,"码农");
     o.sayName();   //"Jack"

第一种当做构造函数使用就不多说了

当在全局作用域中调用Person("Jack",16,"码农");时,this对象总是指向Global对象(浏览器中是window对象)。因此在执行完这句代码后,可以通过window对象来调用sayName()方法,并且返回“Jack”。

最后也可以使用call()或者apply()在某个特殊对象的作用域中调用Person()函数

(3).2存在的问题

在(3)构造函数模式的代码中,对象的方法sayName的功能都一样,就是alert当前对象的name。当实例化Person之后,每个实例(person1和person2)都有一个名为sayName的方法,但是两个方法不是同一个Function实例。不要忘了,js中函数是对象,所以每个实例都包含一个不同的Function实例,然而创建两个功能完全一样的Function实例是完全没有必要的。因此可以把函数定义转移到构造函数外。
如下代码:

     function Person(name,age,job){
            this.name = name;
            this.age = age;
            this.job = job;
            this.sayName = sayName;
        }
    function sayName(){
            alert(this.name);
        }
        
    //实例化对象
    var person1 = new Person("Jack",15,"码农");  //满满的java复古风
    var person2 = new Person("Rose",15,"程序媛");

但是这样依然存在问题:

为了让Person的实例化对象共享在全局作用域中定义的同一个sayName()函数,我们把函数sayName()定义在全局作用域中,并通过指针sayName指向构造函数,所以在全局作用域中的sayName()只能被特定对象调用,全局作用域名不符实,且污染全局变量。

并且如果对象需要很多种方法,那么就要定义很多全局函数,对于对象就没有封装性,并且污染全局。

2.原型 (1)原型模式创建对象

js不同于强类型语言的java,java创建对象的过程是由类(抽象)到类的实例的过程,是一个从抽象到具体的过程。

javascript则不同,其用原型创建对象是一个具体到具体的过程,即以一个实际的实例为蓝本(原型),去创建另一个实例对象。

所以用原型模式创建对象有两种方式:

1.Object.create()方法
Object.create:它接收两个参数,第一个是用作新对象原型的对象(即原型),一个是为新对象定义额外属性的对象(可选,不常用)。

    var Person = {
        name:"Jack",
        job:"码农"
    };
    //传递一个参数
    var anotherPerson = Object.create(Person);
    anotherPerson.name     //"Jack"
    //传递两个参数
    var yetPerson = Object.create(Person,{name:{value:"Rose"}});
    yetPerson.name;        //Rose
    

2.构造函数方法创建对象

任何一个函数都有一个prototype属性(是一个指针),指向通过构造函数创建的实例对象原型对象,原型对象可以让所有对象实例共享它所包含的属性和方法。

因此不必在构造函数中定义对象实例的信息,而是将这些属性和方法直接添加到原型对象中,从而被实例对象多继承(继承后面总结)

    //第一步:用构造函数创建一个空对象
    function Person(){
    }
    //第二步:给原型对象设置属性和方法
    Person.prototype.name = "Jack";
    Person.prototype.age = 20;
    Person.prototype.job = "码农";
    Person.prototype.sayName = function(){
        alert(this.name);
    };
    //第三步:实例化对象后,便可继承原型对象的方法和属性
    var person1 = new Person();
    person1.sayName();           //Jack
    
    var person2 = new Person();
    person2.sayName();           //Jack
    
    alert(person1.sayName == person2.sayName);   //true

person1和person2说访问的是同一组属性和同一个sayName()函数。

(2)理解原型对象

只要创建一个函数,就会为该函数创建一个prototype属性,这个属性指向函数的原型对象。

所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。

当调用构造函数创建一个新的实例对象后,该实例内部会有一个指针([prototype]/_proto_),指向构造函数的原型对象。如下图:

上图中 :

Person.prototype指向了原型对象,而Person.prototype.construstor又指回了Person。

注意观察原型对象,除了包含constructor属性之外,还包括后来添加的其它属性,这就是为什么每个实例化后的对象,虽然都不包含属性和方法,但是都包含一个内部属性指向了Person.prototype,能获得原型中的属性和方法。

(3)判断一个实例对象的原型

这个方法叫:Object.getPrototypeOf(),如下例子:

alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Jack"

这个方法可以很方便的取得一个对象的原型

还可以利用这个方法取得原型对象中的name属性的值。

(3)搜索属性的过程

当我们在创建实例化的对象之后,调用这个实例化的对象的属性时,会先后执行两次搜索。

第一次搜索实例person1有name属性吗?没有进行第二次搜索

第二次搜索person1的原型有name属性吗?有就返回。

因此进行一次思考,如果对实例进行属性重写和方法覆盖之后,访问实例对象的属性和方法会显示哪个?实例对象的还是对象原型的?

    function Person(){
    }
    
    Person.prototype.name = "Jack";
    Person.prototype.age = 20;
    Person.prototype.job = "码农";
    Person.prototype.sayName = function(){
        alert(this.name);
    };
    
    var person1 = new Person();
    var person2 = new Person();
    
    person1.name = "Rose";
    alert(person1.name);        //Rose
    alert(person2.name);       //Jack
    

当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。

但是这个属性只会阻止我们访问原型中的那个属性,而不会修改那个属性
3.使用delete操作符可以删除实例属性,从而重新访问原型中的属性。

    function Person(){
    }

    Person.prototype.name = "Jack";
    Person.prototype.age = 20;
    Person.prototype.job = "码农";
    Person.prototype.sayName = function(){
        alert(this.name);
    };

    var person1 = new Person();
    var person2 = new Person();
    
    person1.name = "Rose";
    alert(person1.name);        //Rose  --来自实例
    alert(person2.name);       //Jack  --来自原型
    
    delete person1.name;
    alert(person1.name);      //Jack  --来自原型
    
(4)判断访问的到底是对象还是原型属性

hasOwnProperty()可以检测一个属性是存在于实例中,还是原型中,只有在给定属性存在于对象实例中,才会返回true。

person1.hasOwnProperty("name");    //假设name存在于原型,返回false

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

"name" in person1   //true

所以通过这两个可以封装一个hasPrototypeProperty()函数确定属性是不是原型中的属性。

function hasPrototypeProperty(object,name){
    return !object.hasOwnProperty(name) && (name in object); 
}
(5)更简单的原型语法

前面每次添加一个属性和方法都要写一次Person.prototype,为了简便可以直接这样

    function Person(){
    }
    Person.prototype = {
        name:"Jack",
        age:20,
        job:"码农",
        sayName:function(){
            alert("this.name");
        }
    };
    

上述代码直接将Person.prototype设置为等于一个以对象字面量形式创建的新对象

上述这么做时:constructor属性就不再指向Person了。

本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数)。

因此如果constructor值很重要,可以在Person.prototype中设置回适当的值:
如上例中可以添加:constructor:Person,

(6)原型的动态性

我们对原型对象所做的任何修改都会立即从实例上反映出来-即使先创建实例对象后修改原型也如此

    var friend = new Person();
    Person.prototype.sayHi = function(){
        alert("Hi");    
    };
    friend.sayHi();      //"Hi"

尽管可以随时为原型添加属性和方法,并且修改能立即在实例对象中体现出来,但是如果重写整个原型对象,就不一样了。看下面例子:

    function Person(){
    }
    var friend =  new Person();
    Person.prototype = {
        constructor:Person,
        name:"Jack",
        age:20,
        sayName:function(){
            alert(this.name);
        }
    };
    friend.sayName();      //error

上述代码先创建了一个Person实例,然后又重写了其原型对象,在调用friend.sayName()时发生错误。

因为friend指向的原型中不包含以该名字命名的属性。关系如下图:

(7)原型对象的问题

省略了为构造函数初始化参数这一环节,结果是所有实例都取得相同的属性,但问题不大,可以为实例对象重写属性来解决。
2.但是,对于包含引用类型值的属性来说,问题就比较突出了,因为引用类型中,属性名只是一个指针,在实例中重写该属性并没有作用。指针始终指向原来的。

如下例子:

function Person(){}

    Person.prototype = {
        constructor:Person,
        name:"Jack",
        job:"码农",
        friends:["路人甲","路人乙","路人丙"],
    
    };
    var person1 = new Person();
    var person2 = new Person();
    
    person1.friends.push("路人丁");
    alert(person1.friends);     //["路人甲","路人乙","路人丙","路人丁"]
    alert(person2.friends);    //["路人甲","路人乙","路人丙","路人丁"]
    alert(person1.friends === person2.friends);     //true

上面这个,假如每个实例对象的引用值属性不一样,则无法修改。

3.组合使用构造函数和原型模式

构造函数模式用于定义实例属性

原型模式用于定义方法和共享的属性

如下代码:

    function Person(name,age,job){
        this.name = name;
        this.job = job;
        this.age = age;
        this.friends = ["路人甲","路人乙"];
    }
    
    Person.prototype = {
        constructor:Person,
        sayName: function(){
            alert(this.name);
        }
    }
    var person1 = new Person("Jack", 20, "码农");
    var person2 = new Person("Rose", 20, "程序媛");
    
    person1.friends.push("路人丁");
    alert(person1.friends);                  //["路人甲","路人乙","路人丁"]
    alert(person2.friends);                  //["路人甲","路人乙"]
    alert(person1.friends === person2.friends);     //false
    alert(person1.sayName === person2.sayName);    //true
4.寄生构造函数模式

该模式基本思想是创建一个函数,该函数作用仅仅是封装创建对象的代码,然后返回新创建的对象。

    function Person(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 friend = new Person("Jack", 16, "码农");
    friend.sayName();          //Jack

构造函数在不返回值的情况下,默认会返回新对象实例。

通过在构造函数末尾添加一个return语句,可以重写调用构造函数时返回的值。

这个方法的用处是:可以创建一个额外方法的特殊的数组(因为原生对象Array的构造函数不能直接修改)

       function SpecialArray(){
            //创建数组
            var values = new Array();
            //添加值
            values.push.apply(values,arguments);
            
            //添加方法
            values.toPipedString = function(){
                return this.join("|");
            };
            //返回数组
            return values;
        }
        var colors = new SpecialArray("black","red","blue");    
        alert(colors.toPipedString());
    

本来想接着写继承的,发现实在太多了,分成两篇吧。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79979.html

相关文章

  • 基础javascript面向对象创建对象原型继承总结(下)

    摘要:当调用的构造函数时,在函数内部又会调用的构造函数,又在新对象上创建了实例属性和,于是这两个属性就屏蔽了原型中的同名属性。 前言:这次对上篇收个尾,主要总结一下javascript的继承。 1.原型链 js中原型链是实现继承的主要方法。基本思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法。我们来简单回顾一下以前的内容: 每个构造函数都有一个原型对象 每个原型对象都包含一个指...

    617035918 评论0 收藏0
  • SegmentFault 技术周刊 Vol.32 - 七夕将至,你的“对象”还好吗?

    摘要:很多情况下,通常一个人类,即创建了一个具体的对象。对象就是数据,对象本身不包含方法。类是相似对象的描述,称为类的定义,是该类对象的蓝图或原型。在中,对象通过对类的实体化形成的对象。一类的对象抽取出来。注意中,对象一定是通过类的实例化来的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 马上就要到七夕了,离年底老妈老爸...

    李昌杰 评论0 收藏0
  • SegmentFault 技术周刊 Vol.32 - 七夕将至,你的“对象”还好吗?

    摘要:很多情况下,通常一个人类,即创建了一个具体的对象。对象就是数据,对象本身不包含方法。类是相似对象的描述,称为类的定义,是该类对象的蓝图或原型。在中,对象通过对类的实体化形成的对象。一类的对象抽取出来。注意中,对象一定是通过类的实例化来的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 马上就要到七夕了,离年底老妈老爸...

    Lyux 评论0 收藏0
  • SegmentFault 技术周刊 Vol.32 - 七夕将至,你的“对象”还好吗?

    摘要:很多情况下,通常一个人类,即创建了一个具体的对象。对象就是数据,对象本身不包含方法。类是相似对象的描述,称为类的定义,是该类对象的蓝图或原型。在中,对象通过对类的实体化形成的对象。一类的对象抽取出来。注意中,对象一定是通过类的实例化来的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 马上就要到七夕了,离年底老妈老爸...

    AaronYuan 评论0 收藏0
  • 总结javascript基础概念(三):js对象原型

    摘要:执行构造函数的一步说明对象可以通过函数来创建。是最顶级的构造函数,对象里面,就有好几个其他属性。构造函数与普通函数并没有区别,只是调用方式不同。 主要问题:1、构造函数和普通函数有区别么?什么区别?2、prototype和__proto__有什么不同?3、instanceof的作用机制,为什么有限制?4、ES6的相关方法,Class继承原理? 三、对象与原型 (一)、数据类型 Js...

    yzd 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<