资讯专栏INFORMATION COLUMN

《JavaScript高级程序设计》——对象学习笔记

appetizerio / 3341人阅读

摘要:在构造函数内部,将属性设置成等于全局的函数。调用构造函数的一个实例后,该实例内部将包含一个指针中称为,指向构造函数的原型对象。原型对象的问题省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都取得相同的属性值。

创建对象

使用对象字面量的形式一个接口会创建很多对象, 会产生大量的重复代码。

工厂模式:用函数来封装以特定接口创建对象的细节
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("Simon", 29, "software Engineer");
var person2 = createPerson("Zaynex",22, "Doctor");

这种模式解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)

构造函数模式
- 可用于创建特定模式的对象,像Object、Array等原生构造函数,在运行时会自动出现在执行环境中。

我们利用构造函数重写下刚才的函数。

function createPerson(name, age, job)
    {
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function()
        {
            alert(this.name);
        };
    }

    var person1 = new Person("Simon",29, "software Engineer");
    var person2 = new Person("Simon",29, "software Engineer");
构造函数与工厂模式的差异

没有显示地创建对象;

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

没有 return 语句;

我们注意到Person开头是大写,按照惯例来讲,构造函数开头字母是大写,非构造函数以小写字母开头。

调用构造函数四步骤

创建一个新对象;

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

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

返回新对象

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

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

对象的constructor属性最初是用来标识对象类型的。但是提到检测对象类型,还是instanceof操作符更可靠一些。

alert(person1 instanceof Object);
alert(person1 instanceof Person);
alert(person2 instanceof Object);
alert(person2 instanceof Person);
//都为true.

我们所创建的所有对象都是Object的实例,同时也是Person的实例。

创建自定义的构造函数意味着将来可以作为实例标识为一种特定的类型;构造函数模式胜过工厂模式的地方

把构造函数当函数
- 任何函数,只要通过  new 操作符来调用,那它就可以作为构造函数;

//当做构造函数使用

var person = new Person("Simon", 29, "software Engineer");
person.sayName(); //Simon

//普通函数调用
Person("Genf", 23, "DOCTOR");  //添加到window
window.sayName();  // Genf
以刚才的那种方式定义的构造函数定义在Global对象中(在浏览器中是window对象),在全局作用域中调用函数时,this指向的是window对象

// 在另外一个对象的作用域中调用
var o = new Object();
Person.call(o, "Kristen", 25, "nusd");
o.sayName(); // Kristen    
构造函数的缺陷

每个方法都要在每个实例上重新创建一遍。

person1 和 person2 都有一个名为 sayName() 的方法;但那两个方法都不是同一个 Function 的实例,因此会有不同的作用域链和标识符解析;不同实例上的同名函数是不同的。

不要忘了,每个函数都是一个对象!所以sayName方法也可以这样写,因此每个Person实例都包含着不同的Function实例。以这种方式创建函数,会导致不同饿作用域和标识符解析。

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

我们可以检验下

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

创建两个完成相同任务的Function 实例没有必要,况且有this对象在,根本不用在执行代码前就把函数绑定到特定对象上面。

我们可以通过函数定义转移构造函数外部来解决这个问题。

function Person(name, age ,job)

{
    this.name = name;
    this.age = age;
    this.sayName = sayName;
}

function sayName()
{
    alert(this.name);
}

var person1 = new Person("Simon", 29, "software Engineer");
var person2 = new Person("Zaynex", 29, "DOCTOR");

把sayName()函数的定义转移到了构造函数外部。

在构造函数内部,将sayName属性设置成等于全局的 sayName 函数。这样sayName 包含的是一个指向函数的指针。 person1和person2共享同一个sayName()函数。

但问题是:
在全局作用域中定义的函数实际上只能被某个对象调用,如果对象需要定义很多方法,那么就要定义多个全局函数。

因此我们需要用原型模式来解决这个问题。

原型模式

我们创建的每个函数都有一个 prototype(原型) 属性,这个属性属于指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例的共享的属性和方法。即通过构造函数而创建的那个对象实例的原型对象。

我们不必将构造函数定义对象实例的信息中,而是可以将这些信息直接添加到对象原型中。

function Person(){
    }

    Person.prototype.name ="Simon";
    Person.prototype.age = 29;
    Person.prototype.job = "software Engineer";
    Person.prototype.sayName = function(){
        alert(this.name);
    };


    var person1 = new Person();
    person1.sayName();

    var person2 = new Person();
    person2.sayName();

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

实际上,person1和person2都不包含属性和方法, 但可以调用person1.sayName().这是通过查找对象属性的过程来实现的。

理解原型对象

无论何时,只要创建了新函数,就会根据一组特定的规则为该函数创建一个 prototype 属性,这个属性指向函数的原型对象。

在默认情况下,所有原型都会自动获得一个constructor(构造函数)属性,这个属性包含在一个指向 prototype属性所在的函数的指针。举例说明: Person.prototype.constructor 指向Person.

创建了自定义构造函数之后,其原型对象默认只会取得 constructor 属性。其他方法都是从Object继承来的。

调用构造函数的一个实例后,该实例内部将包含一个指针(ES5中称为[[Prototype]],指向构造函数的原型对象。在脚本中没有标准形式访问[[Prototype]],但在FF,SF,Chrome中的每个对象都支持属性_proto_;在其他实现中,该属性对脚本不可见。

要明确的是, 这个链接存在于实例与构造函数的原型对象之间,而非实例与构造函数之间。

虽然在现实中无法访问到[[Prototype]],但可以通过 isPrototypeOf()来确定是否存在这种关系。
在ES5中新增一个方法,使用 Object.getPrototypeOf()可以方便的获取一个对象的原型

每当代码读取某个对象的某个属性时,都会执行一次搜索,
1.先从实例本身开始搜索属性,存在,搜索结束。若不存在,执行2
2.从实例的原型开始搜索属性。

继续刚才的代码。如果我们继续给实例添加相同的属性,会怎样?

function Person(){
    }

    Person.prototype.name ="Simon";
    Person.prototype.age = 29;
    Person.prototype.job = "software Engineer";
    Person.prototype.sayName = function(){
        alert(this.name);
    };


    var person1 = new Person();
    var person2 = new Person();
    person1.name = "xiwenzheng";

    alert(person1.name) //xiwenzheng  ——来自实例
    alert(person2.name) // Simon  ——来自原型
    

在person1这个实例中重写属性,那么解释器搜索到了实例本身的属性直接返回,对于person2而言,实例中没有属性,那么再往实例的原型开始搜素属性。

给对象添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性,就是阻止我们访问原型对象,但并不会修改原型对象中的同名属性。即使将person1.name 设置为 null 也不会影响原型对象中的同步属性。

不过delete 实例属性,就可以访问原型对象中的属性了。

function Person(){

    }

    Person.prototype.name ="Simon";
    Person.prototype.age = 29;
    Person.prototype.job = "software Engineer";
    Person.prototype.sayName = function(){
        alert(this.name);
    };
    var person1 = new Person();
    var person2 = new Person();
    person1.name = "xiwenzheng";
    alert(person1.name); //xiwenzheng  ——来自实例
    alert(person2.name); // Simon  ——来自原型
    delete person1.name;
    alert(person1.name); // Simon 来自原型
    

使用hasOwnProperty()可以检测一个属性是否存在实例中还是存在原型中,这个方法只在给定属性存在于对象实例中才会返回 true;

我们继续采用刚才删除部分的整段代码。

alert(person1.hasOwnProperty("name")); // 返回false

原先person1.name是存在对象实例中的(被我们设为了"Zaynex"),但是被我们delete了。
如果我们不delete的话,那就是true了。要想获得原型属性的描述符,必须要在原型对象上调用 Object.hasOwnPropertydDsecriptor();

原型与 in 操作符

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

利用in:判断是否有该属性

利用hasOwnProperty()判断是否存在对象实例中;

结合以后就可以判断该属性是在原型中还是在实例中。

function hasPrototypeProperty(object, name ){

return !object.hasOwnProperty(name) && (name in object);

}
person1.name = "Zaynex";
alert(hasPrototypeProperty(person1, "name")); //false;存在实例中

for-in 循环时,返回的都是通过对象访问的、可枚举的属性(即将[[Enumberable]]标记为true的属性),在ES5中constructor 和 prototype属性的 [[Enumberable]]

设为false,但并不是所有浏览器都照此实现。
想取得对象上所有可枚举的实例属性,可以使用Object.Keys()方法。

function Person(){
    }

    Person.prototype.name ="Simon";
    Person.prototype.age = 29;
    Person.prototype.job = "software Engineer";
    Person.prototype.sayName = function(){
        alert(this.name);
    };

    var keys = Object.keys(Person.prototype);
    alert(keys);//  name ,age, job, sayName

    var p1 = new Person();
    p1.name = "Rob";
    p1.age = 29;

    var p1keys = Object.keys(p1);
    alert(p1keys);  // name ,age 
    

如果想得到所有实例属性,不论是否可枚举,都可以使用 Object.getOwnPropertyNames()

var keys = Object.keys(Person.prototype);
alert(keys);//  constructor, name ,age, job, sayName

更简单的原型语法

之前的例子中每添加一个属性和方法都要 Person.prototype,我们进行适当的封装。

function Person(){

}

Person.prototype = {
    name : "Simon",
    age : 29;
    job : "software Engineer",
    sayName : function  () {
        alert(this.name);
    }
};

我们将 Person.prototype 设置为等于一个以对象字面量形式创建的新对象。
之前介绍到,每创建一个函数,同时会创建它的prototype对象,这个对象会指定获得constructor 属性。而我们在这里的语法本质上是重写了默认的 prototype 对象。

所以 constructor属性也编程了新对象的属性。(指向Object构造函数),不再指向Person了。

instanceof 测试 Object 和 Person 都返回 true,但constructor 属性则等于Object而不等于 Person ;

如果 constructor 的值很重要,则可以特意设置回适当的值

function Person(){
}

Person.prototype = {
    constructor:Person,
    name : "Simon",
    job : "software Engineer",
    sayName : function () {
        alert(this.name);
    }
}

注意,以这种方式重设constructor属性会导致 [[Enumberable]]特性设置为true,但默认我们是不可枚举constructor属性的。

为了兼容ES5的JS引擎,可以用 Object.defineProperty();

function Person(){
    }

    Person.prototype = {
        name : "Simon",
        job : "software Engineer",
        sayName : function () {
            alert(this.name);
        }
    }
Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
});
原型的动态性

在原型中找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能立即从实例中反应出来——即使是先创建实例后修改原型。
不信你看:

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

这个可以归结于实例与原型之间的松散链接关系。我们首先会在实例中搜索sayHi的属性,在没找到的情况下会继续搜索原型,因为实例与原型之间的链接只不过是一个指针。

但是如果重写整个原型对象,情况就不一样了。调用构造函数时会为实例添加一个指向最初原型的[[Prototype]]指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。

请记住,实例中的指针仅指向原型,而不指向构造函数

function Person(){

}

var  friend = new Person();

Person.prototype = {
    constructor:Person,
    name : "Simon",
    job : "software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

friend.sayName();  //error

在这个例子中,我们创建了Person的实例,然后又重写了其原型对象,然后在调用sayName()时发生错误,因此friend指向的原型不包含以该名字命名的属性。

原型对象的问题

省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都取得相同的属性值。

共享导致的问题,很多属性可以共享,对于包含引用类型值的属性来说,问题比较突出。

function Person(){

}

Person.prototype = {
    constructor:Person,
    name : "Simon",
    job : "software Engineer",
    friends : ["Shelby", "Court"],
    sayName : function () {
        alert(this.name);
    }
};

var  person1 = new Person();
var person2 = new Person();

person1.friends.push("Van");

alert(person1.friends === person2.friends) // true;

修改person1.friends 引用的数组,添加字符串,由于 friends数组存在 Person.prototype 而非 person1中,所以修改也会造成person2.friends反映出来,如果我们的初衷就是要共享同一个数组,那无话可说。

可是一般都是要有属于自己的全部属性的。而这个问题正是我们很少看到有人多带带使用原型模式的原因。

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

创建自定义类型的最常见方式就是这个。构造函数用于定义实例属性,原型模式用于定义方法和共享的属性。这样,每个实例都会有自己的一份实例属性的副本,但又同事共享着对方法的引用,最大限度节省了内存。此外,这种混成模式还支持向构造函数传递参数。

function Person (name, age, job){
       this.name = name;
       this.age = age;
       this.job = job;
       this.friends = ["Shelby", "Court"];
   }

   Person.prototype = {
       constructor : Person,
       sayName : function (){
           alert(this.name);
       }
   }


   var person1 = new Person("Nicholas", 29, "software Engineer");
   var person2 = new Person("Greg", 27, "DOCTOR");

   person1.friends.push("Van");
   alert(person1.friends);  // Shelby,Court,Van
   alert(person2.friends); // shelby,Court
   alert(person1.friends === person2.friends); // false
   alert(person1.sayName === person2.sayName); // true
   

实例属性都是在构造函数中定义的,所有实例共享的属性是在 constructor 和方法sayName()是在原型中定义的。

动态原型模式

当其他OO语言经验开发人员看到独立的构造函数和原型时,会感到困惑。因此出现了 动态原型模式———即把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同事使用构造函数和原型的优点。

换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

 function Person (name, age, job){
    //属性
       this.name = name;
       this.age = age;
       this.job = job;
       this.friends = ["Shelby", "Court"];

       if( typeof this.sayName != "function"){
           Person.prototype.sayName = function() {
               alert(this.name);
           };
       }
 }
    // 只有在sayName不存在的情况下, 才将其添加到原型中,这段代码只会在初次调用函数时执行。此后原型已经完成初始化,不需要再做修改。
 var  friends1 = new Person("Nicholas", 29, "software Engineer");
 var friends2 = new Person("Zaynex",19,"Engineer");
 friends1.sayName();
 friends2.sayName();

由于第一次当friends1初始化之后,friends2就不需要再进行初始化原型。
详情参考点击此处

寄生构造函数模式

在上述几种模式都不适用的情况下,我们可以使用寄生构造函数模式
基本思想:创建一个函数,该函数的作用仅仅是封装对象的代码,然后再返回新创建的对象。

 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 ("Nicholas", 29, "software Engineer");
   friend.sayName(); // Nicholas
寄生构造函数应用

在特殊情况下为对象创建构造函数。 假设我们想创建一个具有额外方法的特殊数组,由于不能直接修改Array构造函数,因此可以使用这个模式。

 function SpecialArray(){
          var values = new Array();

           values.push.apply(values, arguments);

           values.toPipedString = function(){
               return this.join("|");
           }

           return values;
    }

    var colors = new SpecialArray("red", "blue", "green");
    alert(colors.toPipedString());  // red|blue|green
    

寄生构造函数模式:返回的对象与构造函数或者与构造函数的原型属性没有关系;不能依赖于 instanceof操作符确定对象类型。因此不建议在已使用其他模式的情况下使用该种模式。

稳妥构造函数模式 应用场景

稳妥对象,是指没有公共属性,其方法也不引用this的对象。适合在安全环境下(这些环境会禁止使用this 和 new),或者放置数据被其他应用程序改动时使用。

稳妥函数与寄生构造函数差异

新创建的对象的实例方法不引用this。

不使用new 操作符调用构造函数。

function Person(name, age, job) {

   var o = new Object();
   //可以在这里定义私有变量和函数。
   //
   //添加方法
   o.sayName = function(){
       alert(name);
   };
   //返回对象
   return o;

}

var friend = Person("Nicholas", 29, "software Engineer");
friend.sayName();
以这种模式创建的对象,除了使用sayName()方法以外,没有其他办法访问name的值。

与计生构造函数模式类似,使用稳妥构造函数模式创建的对象与构造函数之间没有什么关系,因此instanceof操作符对这种对象没有意义。

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

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

相关文章

  • JavaScript高级程序设计学习笔记一(JavaScript简介)

    摘要:在上百种语言中算是命好的一个,还有就是最近纳入高考体系的。由以下三个部分构成。就是对实现该标准规定的各个方面内容的语言的描述。是针对但经过扩展的用于的应用程序编程接口。将页面映射为由节点构成的树状结构。 JavaScript的历史这里就不再赘述了,当然JavaScript的历史还是比较有意思的。在上百种语言中JavaScript算是‘命’好的一个,还有就是最近纳入高考体系的python...

    supernavy 评论0 收藏0
  • JavaScript高级程序设计学习笔记三(基本语法)

    摘要:数据类型中有种简单数据类型也称为基本数据类型和。在中非空字符串,非零数字,任意对象,都被认为。而空字符串,和,,认为是。用于表示整数和浮点数。标识符由数字字母下划线美元符组成,但首字母不能是数字。变量方法对象命名推荐驼峰法。 JavaScript语法 一.语法简介 因为JavaScript语法和Java等语法非常类似。所以只是简单介绍一下。 大小写 JavaScript是大小写敏感的语...

    Mike617 评论0 收藏0
  • 《你不知道的javascript笔记_对象&原型

    摘要:上一篇你不知道的笔记写在前面这是年第一篇博客,回顾去年年初列的学习清单,发现仅有部分完成了。当然,这并不影响年是向上的一年在新的城市稳定连续坚持健身三个月早睡早起游戏时间大大缩减,学会生活。 上一篇:《你不知道的javascript》笔记_this 写在前面 这是2019年第一篇博客,回顾去年年初列的学习清单,发现仅有部分完成了。当然,这并不影响2018年是向上的一年:在新的城市稳定、...

    seasonley 评论0 收藏0
  • JavaScript高级程序设计学习笔记JavaScript中的事件流和事件处理程序

    摘要:可以使用侦听器或处理程序来预订事件,以便事件发生时执行相应的代码。响应某个事件的函数称为事件处理程序或事件侦听器。可以删除通过级方法指定的事件处理程序。 JavaScript和HTML之间的交互是通过事件实现的。 事件:文档或浏览器窗口中发生的一些特定的交互瞬间。 可以使用侦听器(或处理程序来预订事件),以便事件发生时执行相应的代码。 1. 事件流 事件流:从页面中接收事件的顺序。 ...

    Rocko 评论0 收藏0
  • [学习笔记] JavaScript 作用域链

    摘要:全局执行环境的变量对象始终是作用域链中的最后一个变量对象。综上,每个函数对应一个执行环境,每个执行环境对应一个变量对象,而多个变量对象构成了作用域链,如果当前执行环境是函数,那么其活动对象在作用域链的前端。 1.几个概念 先说几个概念:函数、执行环境、变量对象、作用域链、活动对象。这几个东东之间有什么关系呢,往下看~ 函数 函数大家都知道,我想说的是,js中,在函数内部有两个特殊...

    ?xiaoxiao, 评论0 收藏0
  • JavaScript高级程序设计学习笔记之继承模式

    摘要:实现原型链的方式如下让原型对象称为另一个构造函数的实例这个实例继承了的属性上述代码继承是通过来实现,创建的实例,并将该实例赋给。无疑,集两者之大成,这才是最常用的继承模式。 原型链 JavaScript的继承主要依靠原型链来实现的。我们知道,构造函数,原型,和实例之间的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个原型对象的指针。 实现原型链...

    suxier 评论0 收藏0

发表评论

0条评论

appetizerio

|高级讲师

TA的文章

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