JavaScript创建对象之构造函数模式(笔记2)

前一遍笔记已经说过,ECMAScript的构造函数可用来创建特定类型的对象。像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境中。此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。例如,可以使用构造函数模式将前面的例子重写如下。

//构造函数本身也是函数,只不过可以用来创建对象而已。
    function Person(name,age,job) 
    {
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function(){
            alert(this.name);
        };
    }
    //要创建Person的新实例,必须使用new操作符。
     var person1 = new Person('Nicholas',29,'Software Engineer');
     var person2 = new Person('Greg',27,'Doctor');

以这种方式调用构造函数实际上会经历以下4个步骤:
1.创建一个新对象;
2.将构造函数的作用域赋给新对象(因此this就指向了新对象);
3.执行构造函数中的代码(为这个新对象添加属性);
4.返回新对象。

person1和person2分别保存着Person的一个不同的实例。这两个对象都有一个constructor构造函数属性,该属性指向Person,如下所示。

alert(person1.constructor == Person)//true
alert(person2.constructor == Person)//true

但是,提到检测对象类型,还是instanceof操作符要更可靠一些。我们在这个例子中创建的所有对象既是Object实例,同时也是Person的实例,这一点通过instanceof操作符可以得到证实。

alert(person1 instanceof == Object)//true
alert(person1 instanceof == Person)//true
alert(person2 instanceof == Object)//true
alert(person2 instanceof == Person)//true

创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型;而这正是构造函数模式胜过工厂模式的地方。在这个例子中,person1和person2之所以同时是Object的实例,是因为所有对象均继承自Object。(详细内容稍后讨论)。
那么使用new操作符调用Person()与不使用new操作符调用Person()会出现什么结果呢。

//当作构造函数使用
var person = new Person('Nicholas',29,'Software Engineer');
person.sayName(); //"Nicholas"
//作为普通函数调用
Person('Greg',27,'Doctor');
window.sayName();//"Greg"
/*注意:Person函数内的成员(属性和方法)都被添加给window对象了。当在全局作用域中调用一个函数时,this对象总是指向Global对象,在浏览器中就是window对象。*/
//在另一个对象的作用域中调用
var o = new Object();
Person.call(o,"Kristen",25,"Nurse");
o.sayName();//"Kristen"
/*
可以使用call()或apply()在某个特殊对象的作用域中调用Person函数,例如在对象o的作用域中调用Person函数,因此调用后o就拥有了所有属性和sayName()方法。
*/

构造函数的问题
构造函数模式虽然好 ,但也并非没有缺点。使用构造函数的主要问题,就是每个方法都 要在每个实例上重新创建一遍。在前面的例子中,person1和person2都有一个名为sayName()的方法,但那两个方法不是同一个Function的实例。不要忘了ECMAScript中的函数是对象,因此每定义一个函数,也就是实例化了一个对象。从逻辑角度讲,此时的构造函数也可以这样定义。

function Person(name,age,job) 
    {
        this.name = name;
        this.age = age;
        this.job = job;
       // this.sayName = function(){
       //     alert(this.name);
       // };
        this.sayName = new Function(
            "alert(this.name)"
        );
        /*
        以这种方式创建函数,会导致不同的作用域链和标签解析,但创建Function新实例的机制仍然是相同的。因此,不同实例上的同名函数是不相等的,以下代码可以证明这一点。
        */
    }
    
     var person1 = new Person('Nicholas',29,'Software Engineer');
     var person2 = new Person('Greg',27,'Doctor');
     alert(person1.sayName == person2.sayName);//false

创建两个完成同样任务的Function实例的确没有必要;况且有this对象在,根本不用在执行代码前就把function()函数绑定到特定对象上面。可以通过把function函数定义转移到构造函数外部来解决这个问题。

function Person(name,age,job) 
    {
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = sayName;
    }
//person1与person2对象共享同一个sayName()函数
function sayName()
{
    alert(this.name);
}
var person1 = new Person('Nicholas',29,'Software Engineer');
person1.sayName();
var person2 = new Person('Greg',27,'Doctor');
person2.sayName();

在构造函数内部,将sayName属性设置成等于全局的sayName函数。这样一来,由于sayName包含的是一个指向函数的指针,因此person1和person2对象就共享了在全局作用域中定义的同一个sayName()函数。这样做确实解决了两个函数做同一件事的问题,可是新问题又来了:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。而更让人无法接受的是:如果对象需要定义很多方法,那么就要定义很多个全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。好在,这些问题可以通过使用原型模式来解决。

发布了40 篇原创文章 · 获赞 0 · 访问量 1531

猜你喜欢

转载自blog.csdn.net/zhoumoon/article/details/105283281