zoukankan      html  css  js  c++  java
  • 《JavaScript 高级程序设计》学习总结六(2)

    引言:本章节继续对原型进行学习总结,总结内容大概为:原型语法,以及原型的几种创建使用方式:原型模式的缺点,构造函数与原型模式一起使用的混合模式等等。

    更简单的原型语法:

    上一章节中,我们已经了解到如何使用原型,但是前面的例子中,我们每次使用一次原型都要敲 Person.prototype 这样是很麻烦的,所以在这里我们介绍一种更简单的方式,那就是:

     1 function Person(){
     2 
     3 }
     4 
     5 Person.prototype={
     6 
     7   name :“jack”,
     8 
     9   age: 21;
    10 
    11   job: "Eont Edf engineer",
    12 
    13   syaName : function(){
    14 
    15   alert(this.name);
    16 
    17 }
    18 
    19 };

     通过这样字面量的方式,一次性将属性可以简写很多代码,但是这样也有一个缺点,那就是这个原型对象的 constructor 属性不再指向 函数 Person 了,上一章节我们提到过,当我们创建一个函数时,就会同时自动创建它的 prototype 属性,这个对象也会自动获得它的 constructor 属性。而这里我们采用这样的字面量语法相当于重写了 默认的 prototype 对象,因此此时 这个 constructor 属性就也变成了新的 construnctor (指向 Object 构造函数)。如果此时我们创建 person 的实例比如 :

    var friende=new Person();
    
    alert(friend instanceof Object); //true
    
    alert(friend instanceof Person); //true
    
    alert(friend.constructor == Person); //false
    
    alert(friend.constructor == Object); //true

    这里我们可以看出,虽然 instanceof 值没有改变,但是constructor 的指向已经不是 Person了,而是 Object。 如果此时我们需要它指回 Person ,我们可以这么写:

     1 function Person(){
     2 
     3 }
     4 
     5 Person.prototype={
     6 
     7   constructor:Person, 
     8 
     9     name :“jack”,
    10 
    11   age: 21;
    12 
    13   job: "Eont Edf engineer",
    14 
    15   syaName : function(){
    16 
    17   alert(this.name);
    18 
    19 }
    20 
    21 };

    // 直接在重写 prototype 的时候将这个 constructor 写进去,并将其指向 Person。注意,以这种方式重设 constructor 属性会导致它的[[Enumerable]]特性被设置为 true。默认 情况下,原生的 constructor 属性是不可枚举的,因此如果你使用兼容 ECMAScript 5 的 JavaScript 引 擎,可以试一试 Object.defineProperty()。

    重写上面代码:

     1 function Person(){
     2 
     3 }
     4 
     5 Person.prototype={
     6 
     7   constructor:Person, 
     8 
     9     name :“jack”,
    10 
    11   age: 21;
    12 
    13   job: "Eont Edf engineer",
    14 
    15   syaName : function(){
    16 
    17   alert(this.name);
    18 
    19 }
    20 
    21 };
    22 
    23 //重设构造函数,只适用于 ECMAScript 5 兼容的浏览器
    24 
    25 Object.defineProperty(Person.prototype, "constructor", {
    26 
    27 enumerable: false, value: Person
    28 
    29 });
    30 
    31  
    32 
    
     原型的动态性:
     
     直接看这个例子:
     
     var friend = new Person();
     
     Person.prototype.sayHi = function(){
     
    alert("hi");
     
     };
    
     friend.sayHi(); //"hi"(没有问题!)

    以上代码先创建了 Person 的一个实例,并将其保存在 person 中。然后,下一条语句在 Person. prototype 中添加了一个方法 sayHi()。即使 person 实例是在添加新方法之前创建的,但它仍然可 以访问这个新方法。其原因可以归结为实例与原型之间的松散连接关系当我们调用 person.sayHi() 时,首先会在实例中搜索名为 sayHi 的属性,在没找到的情况下,会继续搜索原型。因为实例与原型 之间的连接只不过是一个指针,而非一个副本,因此就可以在原型中找到新的 sayHi 属性并返回保存 在那里的函数

     这里提一下:尽管可以随时为原型添加属性和方法,并且修改能够立即在所有对象实例中反映出来,但如果是重 写整个原型对象,那么情况就不一样了。我们知道,调用构造函数时会为实例添加一个指向最初原型的 [[Prototype]]指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。 请记住:实例中的指针仅指向原型,而不指向构造函数。(这句话很重要)看下面的例子

     1 function Person(){ }
     2 
     3 var friend = new Person();
     4 
     5 Person.prototype = {
     6 
     7 constructor: Person,
     8 
     9 name : "Nicholas",
    10 
    11 age : 29,
    12 
    13 job : "Software Engineer",
    14 
    15 sayName : function () {
    16 
    17 alert(this.name);
    18 
    19 } };
    20 
    21 friend.sayName(); //error

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

    从上面的“简单的原型语法” 可以知道,我们使用字面量声明原型,把那个添加属性的时候,相当于重写了默认的 prototype 原型,也就是创建了新的 prototype 原型,此时创建这个实例的构造函数指向的是这个新的原型,而最初的原型与构造函数之间的“链”就断了。遵从实例中的指针仅指向原型,而不指向构造函数。   的原则,friende 实例一直指向最初的 prototype 原型,而我们在进行 friende.sayName( ) 的调用时,会先在实例中查找这个方法,查找不到,就会去原型中查找,而这个最初的原型中不包含 sayName( )方法,所以就会报错。用图表示:

    如图,这是我们最初创建函数与实例时的样子,这里我补充一句:实例中的指针仅指向原型,而不指向构造函数。这个确切的说,应该这样表达才准确:实例中的指针仅指向最初的原型,而不指向构造函数。原因如下:

    此时可以看到,始终如一的实例 friend 依旧指向最初的原型,最初的原型对象的constructor 属性也依旧指向构造函数,但是构造函数却已经“变了心了”,它指向了新的原型对象。如此一来,构造函数与最初的原型之间的关系就被切断了。(横刀夺爱啊)。

    原生对象的原型:

    从原型的用法我们可以知道,通过原型我们可以给对象添加属性,或者函数,有趣的是 JavaScript 中原生对象也支持这样的方式,并且在原生对象中的原有的方法,也可以在其原型中找到,比如 : Array() 的原型中就包含着 sort( )方法。所以我们也可以向这些原生对象中添加属性和方法。不过值得一说的是,虽然我们可以这么做,但是事实上这样的做法是不应该支持的,因为如果因 某个实现中缺少某个方法,就在原生对象的原型中添加这个方法,那么当在另一个支 持该方法的实现中运行代码时,就可能会导致命名冲突。而且,这样做也可能会意外 地重写原生方法。 这样带来的后果和代码维护是十分麻烦的。

     

    原型对象问题:

    原型模式也不是没有缺点。首先,它省略了为构造函数传递初始化参数这一环节结果所有实例在 默认情况下都将取得相同的属性值原型的共享特性让我们可以共享到数据,这种方式对于函数来说非常合适,但是对于引用类型值的属性 来说就出现了一个问题

     1 function Person(){ }  //构造函数没有传递参数
     2 
     3 Person.prototype = {
     4 
     5 constructor: Person,
     6 
     7 name : "Nicholas",
     8 
     9 age : 29,
    10 
    11 job : "Software Engineer",
    12 
    13 friends : ["Shelby", "Court"],
    14 
    15 sayName : function () { alert(this.name);
    16 
    17 } };
    18 
    19 var person1 = new Person();
    20 
    21 var person2 = new Person();
    22 
    23 person1.friends.push("Van");
    24 
    25 alert(person1.friends); //"Shelby,Court,Van"
    26 
    27 alert(person2.friends); //"Shelby,Court,Van"
    28 
    29 alert(person1.friends === person2.friends); //true
    30 
    31  

    在此,Person.prototype 对象有一个名为 friends 的属性,该属性包含一个字符串数组。然后, 创建了 Person 的两个实例。接着,修改了 person1.friends 引用的数组,向数组中添加了一个字符 串。由于 friends 数组存在于 Person.prototype 而非 person1 中,所以刚刚提到的修改也会通过 person2.friends(与 person1.friends 指向同一个数组)反映出来。假如我们的初衷就是像这样 在所有实例中共享一个数组,那么对这个结果我没有话可说。可是,实例一般都是要有属于自己的全部属性的。而这个问题正是我们很少看到有人单独使用原型模式的原因所在

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

     看了上面的原型模式,我们知道,原型模式也是有自己的缺点的,省略了为构造函数传递初始化参数这一环节,结果所有实例在 默认情况下都将取得相同的属性值,所以一般来说创建自定义类型最常见的方式,就是组合使用构造函数与原型模式(让构造函数传参,让原型模式共享)。这样一来就实现每个实例拥有自己的属性,同时也能够共享对方法的引用(上面我们也提到,共享方式对函数是有优势的)。代码例子:

     1 function Person(name, age, job){
     2 
     3 this.name = name;
     4 
     5 this.age = age;
     6 
     7 this.job = job;
     8 
     9 this.friends = ["Shelby", "Court"];
    10 
    11 } //构造函数定义属性
    12 
    13 Person.prototype = { constructor : Person, //原型定义共享函数
    14 
    15 sayName : function(){ alert(this.name);
    16 
    17 } }
    18 
    19 var person1 = new Person("Nicholas", 29, "Software Engineer");
    20 
    21 var person2 = new Person("Greg", 27, "Doctor");
    22 
    23 person1.friends.push("Van");
    24 
    25 alert(person1.friends); //"Shelby,Count,Van"
    26 
    27 alert(person2.friends); //"Shelby,Count"
    28 
    29 alert(person1.friends === person2.friends); //false
    30 
    31 alert(person1.sayName === person2.sayName); //true

    实例属性都是在构造函数中定义的,而由所有实例共享的属性 constructor 和方 法 sayName()则是在原型中定义的。而修改了 person1.friends(向其中添加一个新字符串),并不 会影响到 person2.friends,因为它们分别引用了不同的数组。 这样的方式一举两得,同时对代码的可读性也是有一定的提升,比较推荐这样的方式。

    动态原型模式:

     有其他 OO 语言经验的开发人员在看到独立的构造函数和原型时,很可能会感到非常困惑。动态原 型模式正是致力于解决这个问题的一个方案,它把所有信息都封装在了构造函数中,而通过在构造函数 中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过 检查某个应该存在的方法是否有效,来决定是否需要初始化原型。来看一个例子。

     1 function Person(name, age, job){ //属性
     2 
     3 this.name = name;
     4 
     5 this.age = age;
     6 
     7 this.job = job;
     8 
     9 //方法
    10 
    11 if (typeof this.sayName != "function"){
    12 
    13 Person.prototype.sayName = function(){
    14 
    15 alert(this.name);
    16 
    17 }; } }
    18 
    19 var friend = new Person("Nicholas", 29, "Software Engineer");
    20 
    21 friend.sayName();

    注意构造函数代码中加粗的部分。这里只在 sayName()方法不存在的情况下,才会将它添加到原 型中。这段代码只会在初次调用构造函数时才会执行。此后,原型已经完成初始化,不需要再做什么修 改了。不过要记住,这里对原型所做的修改,能够立即在所有实例中得到反映。因此,这种方法确实可 以说非常完美。其中,if 语句检查的可以是初始化之后应该存在的任何属性或方法——不必用一大堆 if 语句检查每个属性和每个方法;只要检查其中一个即可。对于采用这种模式创建的对象,还可以使 用 instanceof 操作符确定它的类型

    使用动态原型模式时,不能使用对象字面量重写原型。前面已经解释过了,如果 在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。

    寄生构造函数模式:

    通常,在前述的几种模式都不适用的情况下,可以使用寄生(parasitic)构造函数模式。这种模式 的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;但 从表面上看,这个函数又很像是典型的构造函数。下面是一个例子

     1 function Person(name, age, job){
     2 
     3 var o = new Object();
     4 
     5 o.name = name;
     6 
     7 o.age = age;
     8 
     9 o.job = job;
    10 
    11 o.sayName = function(){
    12 
    13 alert(this.name); };
    14 
    15 return o; }
    16 
    17 var friend = new Person("Nicholas", 29, "Software Engineer");
    18 
    19 friend.sayName(); //"Nicholas"

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

     1 function Person(name, age, job){
     2 
     3 var o = new Object();
     4 
     5 o.name = name;
     6 
     7 o.age = age;
     8 
     9 o.job = job;
    10 
    11 o.sayName = function(){
    12 
    13 alert(this.name); };
    14 
    15 return o; }
    16 
    17 var friend = new Person("Nicholas", 29, "Software Engineer");
    18 
    19 friend.sayName(); //"Nicholas"

    在这个例子中,我们创建了一个名叫 SpecialArray 的构造函数。在这个函数内部,首先创建了 一个数组,然后 push()方法(用构造函数接收到的所有参数)初始化了数组的值。随后,又给数组实 例添加了一个 toPipedString()方法,该方法返回以竖线分割的数组值。最后,将数组以函数值的形 式返回。接着,我们调用了 SpecialArray 构造函数,向其中传入了用于初始化数组的值,此后又调 用了 toPipedString()方法。

    关于寄生构造函数模式,有一点需要说明:首先,返回的对象与构造函数或者与构造函数的原型属 性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。为此, 不能依赖 instanceof 操作符来确定对象类型。由于存在上述问题,我们建议在可以使用其他模式的情 况下,不要使用这种模式。

    稳妥构造函数模式:

    道格拉斯·克罗克福德(Douglas Crockford)发明了 JavaScript 中的稳妥对象(durable objects)这 个概念。所谓稳妥对象,指的是没有公共属性,而且其方法也不引用 this 的对象。稳妥对象最适合在 一些安全的环境中(这些环境中会禁止使用 this 和 new),或者在防止数据被其他应用程序(如 Mashup 程序)改动时使用。稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的 实例方法不引用 this;二是不使用 new 操作符调用构造函数。按照稳妥构造函数的要求,可以将前面 的 Person 构造函数重写如下。

    function Person(name, age, job){

    //创建要返回的对象

    var o = new Object();

    //可以在这里定义私有变量和函数 //添加方法

    o.sayName = function(){ alert(name); };

    //返回对象

    return o;

    }

    注意,在以这种模式创建的对象中,除了使用 sayName()方法之外,没有其他办法访问 name 的值。 可以像下面使用稳妥的 Person 构造函数。

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

    friend.sayName(); //"Nicholas"

    这样,变量 friend 中保存的是一个稳妥对象,而除了调用 sayName()方法外,没有别的方式可 以访问其数据成员。即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问传 入到构造函数中的原始数据。稳妥构造函数模式提供的这种安全性,使得它非常适合在某些安全执行环 境——例如,ADsafe(www.adsafe.org)和 Caja(http://code.google.com/p/google-caja/)提供的环境—— 下使用。

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

    --------------------------------------------------------------------------------------------------------本章节完------------------------------------------------------------------------------------------------------------------

    下章节预告:我们知道,JavaScript 中没有类的概念,那么如此众多的对象时怎么联系在一起呢?如何实现子类父类这样的继承呢?下一章节,我们对JavaScript 的继承进行总结,通过讲解原型链实现继承,让我们能够更详细的理解JavaScript 继承机制的实习。以及各种继承方式实现的优劣。

  • 相关阅读:
    记php多张图片合成一张图片 压缩固定分辨率 合并生成竖列 纵向长图(可用于商品详情图合并下载)
    记php-mysql分页查询出现重复数据
    记laravel order by 问题
    记登录注册时候 前端js明文密码 加密传输 php解密
    记下载oss图片接口(附带删除)
    记tp5.1使用composer PhpOffice的xlsx表格文件导入数据库
    记php移动并压缩多级目录文件为zip文件并上传oss
    Jmeter服务器性能监控工具插件之ServerAgent
    Jmeter阶梯式加压测试
    Jmeter 下载+安装+汉化+版本更新+备份使用(Jmeter 4+版本均适用)
  • 原文地址:https://www.cnblogs.com/wxhhts/p/9473813.html
Copyright © 2011-2022 走看看