面向对象,构造函数的出现

日期:2019-11-26编辑作者:澳门金莎娱乐手机版

在全局执行环境中使用this,标识Global对象,在浏览器中就是window对象。 当在函数执行环境中使用this时,如果函数没有明显的作为非window对象的属性,而是只是定义了函数,不管这个函数是不是定义在另一个函数中,这个函数中的this仍然标识window对象。如果函数显示地作为一个非window对象的属性,那么函数中的this就代表这个对象。 复制代码 代码如下: var o=new Object; o.func=function; { alert 当通过new运算符来调用函数时,函数被当作一个构造函数,this指向构造函数创建出来的对象。 更详细的可以参考Javascript this指针

构造函数的定义

ECMAScript中的构造函数可用来创建特定类型的对象。像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境中。此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。

构造函数创建对象

function Person(name, age, job){

this.name = name;

this.age = age;

this.job = job;

this.sayName = function(){

alert(this.name);

};

}

var person1 = new Person("Nicholas", 29, "Software Engineer");

var person2 = new Person("Greg", 27, "Doctor");

在这个例子中,Person()函数取代了createPerson()函数。我们注意到,Person()中的代码

除了与createPerson()中相同的部分外,还存在以下不同之处:

没有显式地创建对象;

直接将属性和方法赋给了this对象;

没有return语句。

此外,还应该注意到函数名Person使用的是大写字母P。按照惯例,构造函数始终都应该以一个

大写字母开头,而非构造函数则应该以一个小写字母开头。这个做法借鉴自其他OO语言,主要是为了

区别于ECMAScript中的其他函数;因为构造函数本身也是函数,只不过可以用来创建对象而已。

要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤:

(1)创建一个新对象;

(2)将构造函数的作用域赋给新对象(因此this就指向了这个新对象);

(3)执行构造函数中的代码(为这个新对象添加属性);

(4)返回新对象。

在前面例子的最后,person1和person2分别保存着Person的一个不同的实例。这两个对象都

有一个constructor(构造函数)属性,该属性指向Person,如下所示。

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

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

对象的constructor属性最初是用来标识对象类型的。但是,提到检测对象类型,还是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。

Javascript中的对象

构造函数和普通函数的区别

构造函数与其他函数的唯一区别,就在于调用它们的方式不同。不过,构造函数毕竟也是函数,不存在定义构造函数的特殊语法。任何函数,只要通过new操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过new操作符来调用,那它跟普通函数也不会有什么两样。例如,前面例子中定义的Person()函数可以通过下列任何一种方式来调用。

例如:

//当作构造函数使用

var person = new Person("Nicholas", 29, "Software Engineer");

person.sayName(); //"Nicholas"

//作为普通函数调用

Person("Greg", 27, "Doctor"); //添加到window

window.sayName(); //"Greg"

//在另一个对象的作用域中调用

var o = new Object();

Person.call(o, "Kristen", 25, "Nurse");o.sayName(); //"Kristen"

这个例子中的前两行代码展示了构造函数的典型用法,即使用new操作符来创建一个新对象。接下来的两行代码展示了不使用new操作符调用Person()会出现什么结果:属性和方法都被添加给window对象了。有读者可能还记得,当在全局作用域中调用一个函数时,this对象总是指向Global对象(在浏览器中就是window对象)。因此,在调用完函数之后,可以通过window对象来调用sayName()方法,并且还返回了"Greg"。最后,也可以使用call()(或者apply())在某个特殊对象的作用域中调用Person()函数。这里是在对象o的作用域中调用的,因此调用后o就拥有了所有属性和sayName()方法。

什么是对象

构造函数的问题

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

function Person(name, age, job){

this.name = name;

this.age = age;

this.job = job;

this.sayName = new Function("alert(this.name)"); //与声明函数在逻辑上是等价的}

从这个角度上来看构造函数,更容易明白每个Person实例都包含一个不同的Function实例(以显示name属性)的本质。说明白些,以这种方式创建函数,会导致不同的作用域链和标识符解析,但创建Function新实例的机制仍然是相同的。因此,不同实例上的同名函数是不相等的,以下代码可以证明这一点。

alert(person1.sayName == person2.sayName);  //false

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

例子:

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("Nicholas", 29, "Software Engineer");

var person2 = new Person("Greg", 27, "Doctor");

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

  我们可以把Javascript中对象理解为一组无序的键值对,就好像C#中的Dictionary<string,Object>一样。Key是属性的名称,而value可以为以下3种类型:

 

基本值(string, number, boolean, null, undefined)

对象

函数

复制代码

var o = new Object();

o["name"] = "jesse";  //基本值作为对象属性

o["location"] = {     //对象作为对象属性

    "city": "Shanghai",

    "district":"minhang"

};

 

// 函数 作为对象属性

o["sayHello"] = function () {

    alert("Hello, I am "+ this.name + " from " + this.location.city);

}

 

o.sayHello();

复制代码

遍历属性

  在C#中我们是可以用foreach对Dictionary<string,Object>进行遍历的,如果说对象在Javascript中是一组键值对的话,那我们如何进行遍历呢?

 

复制代码

for (var p in o) {

    alert('name:'+ p + 

          ' type:' + typeof o[p]

        );

}

// name:name type:string

// name:location type:object

// name:sayHello type:function

复制代码

  上面的这种遍历方式会把原型中的属性也包括进来,关于什么是原型,以及如何区分原型和实例中的属性我们下面会讲到。

 

创建对象

  其实在上面我们已经创建了一个对象,并且使用了以下两种创建对象的方式。

 

利用new创建一个Object的实例。

字面量

  我们上面的o是用第一种方式创建的,而o中的location属性则是用字面量的方式创建的。而第一种方式其实也有一种名字叫做构造函数模式,因为Object实际上是一个构造函数,为我们产生了一个Object的实例。如果对于构造函数这一块还有不清楚的话,赶紧去看我的第一篇 类型基础Object与object吧。

 

  除了以上两种方式以外,我们一些创建对象的方式,我们也来一起看一下:

 

工厂模式

复制代码

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('Jesse', 29, 'Software Engineer');

var person2 = createPerson('Carol', 27, 'Designer');

复制代码

  这种模式创建的对象有一个问题,那就是它在函数的内部为我创建了一个Object的实例,这个实例跟我们的构造函数createPerson是没有任何关系的。

 

  

 

  因为我在内部用new Object()来创建了这个对象,所以它是Object的实例。所以如果我们想知道它是具体哪个function的实例,那就不可能了。

 

构造函数模式

  工厂模式没有解决对象识别的问题,但是我们可以想一下,Object()实际上也是一个函数,只不过当我在它前面加上一个new的时候,它就变成了一个构造函数为我们产生一个Object的实例。那么我同样也可以在其它函数前面加上new这样就可以产生这个函数的实例了,这就是所谓的构造函数模式。

 

复制代码

function Person(name, age, job){

    this.name = name;

    this.age = age;

    this.job = job;

    this.sayName = function(){

        alert(this.name);

    };

}

 

var p1 = new Person('Jesse', 18, 'coder');

alert(p1 instanceof Person); // true

复制代码

详解this

   this在Javascript中也可以算是一个很神奇对象,没错this是一个对象。我们在上一篇作用域和作用域链中讲到了变量对象,变量对象决定了在当前的执行环境中有哪些属性和函数是可以被访问到的,从某种程度上来说我们就可以把this看作是这个变量对象。我们之前提到了最大的执行环境是全局执行环境,而window就是全局执行环境中的变量对象,那么我们在全局环境中this===window是会返回true的。

 

  

 

  除了全局执行环境以外,我们还提到了另外一种执行环境,也就是函数。每一个函数都有一个this对象,但有时候他们所代表的值是不一样的,主要是这个函数的调用者来决定的。我们来看一下以下几种场景:

 

函数

function f1(){

  return this;

}

 

f1() === window; // global object

  因为当前的函数在全局函数中运行,所以函数中的this对象指向了全局变量对象,也就是window。这种方式在严格模式下会返回undefined。

 

对象方法

复制代码

var o = {

  prop: 37,

  f: function() {

    return this.prop;

  }

};

 

console.log(o.f()); // logs 37

复制代码

  在对象方法中,this对象指向了当前这个实例对象。注意: 不管这个函数在哪里什么时候或者怎么样定义,只要它是一个对象实例的方法,那么它的this都是指向这个对象实例的。

 

复制代码

var o = { prop: 37 };

var prop = 15;

 

function independent() {

    return this.prop;

}

 

o.f = independent;

console.log(independent()); // logs 15

console.log(o.f()); // logs 37

复制代码

 区别:上面的函数independent如果直接执行,this是指向全局执行环境,那么this.prop是指向我们的全局变量prop的。但是如果将independent设为对象o的一个属性,那么independent中的this就指向了这个实例,同理this.prop就变成了对象o的prop属性。

 

构造函数

   我们上面讲到了用构造函数创建对象,其实是利用了this的这种特性。在构造函数中,this对象是指向这个构造函数实例化出来的对象。

 

复制代码

function Person(name, age, job) {

    this.name = name;

    this.age = age;

澳门金莎娱乐手机版 ,    this.job = job;

    this.sayName = function () {

        alert(this.name);

    };

}

 

var p1 = new Person('Jesse', 18, 'coder');

var p2 = new Person('Carol',16,'designer');

复制代码

  当我们实例化Person得到p1的时候,this指向p1。而当我们实例化Person得到p2的时候,this是指向p2的。

 

利用call和apply

  当我们用call和apply去调用某一个函数的时候,这个函数中的this对象会被绑定到我们指定的对象上。而call和apply的主要区别就是apply要求传入一个数组作为参数列表。

 

复制代码

function add(c, d) {

    return this.a + this.b + c + d;

}

 

var o = { a: 1, b: 3 };

 

// 第一个参数会被绑定成函数add的this对象

add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16

 

// 第二个参数是数组作为arguments传入方法add

add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

复制代码

在bind方法中

  bind方法是 存在于function的原型中的 Function.prototype.bind,也就是说所有的function都会有这个方法。但我们调用某一个方法的bind的时候,会产生一个和原来那个方法一样的新方法,只不过this是指向我们传得bind的第一个参数。

 

复制代码

function f() {

    return this.a;

}

 

var g = f.bind({ a: "azerty" });

console.log(g()); // azerty

 

var o = { a: 37, f: f, g: g };

console.log(o.f(), o.g()); // 37, azerty

复制代码

在dom元素事件处理器中

  在事件处理函数中,我们的this是指向触发这个事件的dom元素的。

 

HTML代码

 

复制代码

<html>

<body>

    <div id="mydiv" style="width:400px; height:400px; border:1px solid red;"></div>

    <script type="text/javascript" src="essence.js"></script>

</body>

</html>

复制代码

JavaScript代码

 

function click(e) {

    alert(this.nodeName);

}

 

var myDiv = document.getElementById("mydiv");

myDiv.addEventListener('click', click, false);

  当我们点击页面那个div的时候,毫无疑问,它是会显示DIV的。

什么是对象 我们可以把Javascript中对象理解为一组无序的键值对,就好像C#中的Dictionarystring,Object一样。Key是属性的名称,而...

本文由澳门金莎娱乐网站发布于澳门金莎娱乐手机版,转载请注明出处:面向对象,构造函数的出现

关键词:

in锚点的动态创建,收集整理的四个方向的滚动

[Ctrl+A 全选 注:如需引入外部Js需刷新才能执行] 向左滚动 测试状态栏字符的滚动 JavaScript 无缝左右滚动加定高定宽停...

详细>>

拖放效果代码_javascript技巧_脚本之家,区域外事

不过setCapture不支持键盘事件,只能捕获以下鼠标事件:onmousedown、onmouseup、onmousemove、onclick、ondblclick、onmouseover和onm...

详细>>

javascript执行效率澳门金莎娱乐手机版:,高效运

作者 Mark 'Tarquin' Wilton-Jones · 2006年11月2日 有效提高JavaScript执行效率的几点知识,javascript执行效率 为了提供新鲜、别...

详细>>

文字滚动效果_javascript技巧_脚本之家,实现滚动

1.先写两个最常用最简洁的滚动代码 代码如下: 水平滚动:水平滚动字幕内容 垂直滚动: 垂直滚动字内容 2.平稳不...

详细>>