• 【javascript高级程序设计笔记】第六章OOP


    忙了一段时间,加了将近一个月的班。 书也落下没看,上次看到第七章(这部分笔记大概还是9月份的吧),偶尔看到很吃力。看的速度慢下来。

    学习就是一个慢慢积累慢慢沉淀的过程。看书时没有明显觉得提升。但在看完书后近段时间工作中写代码,明显感觉效率还是有提升,基础知识牢固了。    

    这本书是第二次看,这次很认真的遍读和做笔记,笔记的文字均是自己边看边敲的,这样才更好的真正的吸收到一部分吧!

    这些天在看web响应式设计:HTML5和CSS3实战 

    第6章 面向对像的程序设计

    6.1.1属性类型

    ECMAScript中有二种属性:数据性性和访问器属性。

    1.数据属性

    数据性性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有4个描述其行为的特性。

    [[Configurable]]:表示能否通过delete删除属性从而重新定义属性。能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值是true。

    [[Enumerable]]:表示能否通过for-in循环返回属性。像前面例子中那样直接在在对象上定义属性,它们的这个特性默认值为true。

    [[Writable]]:表示能否修改属性的值,像前面例子中那样直接在对象上定义属性,默认值true

    [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读写入属性值的时候,把新值保存在这个位置。默认为undefined

    要修改属性的特性必须使用

    Object.defineProperty()

    这个方法接收三个参数:属性所在的对象、属性的名字、一个描述符对象。

    描述符(descriptor)对象的属性必须是:configurable,enumerable,writable,value

    设置其中的一或多个值,可以修改对应的特性值。

    2.访问器属性

    [[Configurable]]: 表示能否通过delete删除属性从而重新定义属性。能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,它们的这个特性默认值是true。

    [[Enumerable]]: 表示能否修改属性的值,像前面例子中那样直接在对象上定义属性,默认值true

    [[Get]]:在读取属性时调用的函数。默认undefined

    [[Set]]:在写入属性时调用的函数,默认undefined

    访问器属性不能直接定义,必须使用Object.defineProperty()来定义

    6.1.2定义多个属性

    由于为对象定义多个属性的可能性很大。

    ECMAScript 又定义了一个Object.defineproperties()方法

    利用这个方法可以通过描述符一次定义多个属性。这个方法接收二个对象参数:第一个对象是要添加和修改其属性的对象,第二个对象的属性是第一个对象中要添加或修改的属性一一对应。

    6.1.3 读取属性的特性

    Object.getOwnPropertyDescriptor()方法可以取得给定属性的描述符。

    这个方法接收两个参数:属性所在的对象和要读取其描述属性的名称。返回值是一个对象。

    在javascript可以对任何对象,包括DOM BOM使用Object.getOwnPropertyDescriptor()方法。

    6.2 创建对象

    6.2.1 工厂模式

    function creatPerson(name,age,job){

             var o = new Object();

             o.name = name;

             o.age = age;

             o.job = job;

             o.sayName = function(){

                       alert(this.name);

    }

    }

    Var person1 = creatPerson(“wang”,27,”web前端”)

    var person2 = creatPerson(“lee”,33,”java开发”)

    6.2.2 构造函数模式

    function Person(name,age,job){

             this.name = name;

             this.age = age;

             this.job = job;

             this.sayName = funciont(){

                       alert(this.name)

    }

    }

    var person1 = new Person(“wang”,27,”web前端”)

    var person2 = new Person(“lee”,33,”java开发”)

    构造函数模式虽然好用,但也并非没有缺点,使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。

    在上一个例子中person1,person2都有一个名为sayName()方法,但二个方法不是同一个function的实例。ECMAScript中的函数是对象,因此每定义一个函数,也就是实例化一个对象。

    创建两个完成同样任务的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(“wang”,27,”web前端”)

    var person2 = new Person(“lee”,33,”java开发”)

    6.2.3 原型模式

    function Person(){}

    Person.prototype.name = “wang”;

    Person.prototype.age = 27;

    Person.prototype.job =”web”;

    Person.prototype.sayName = function(){

             Alert(this.name);

    }

    var person1 = new Person();

    peson1.sayName()       //wang

    1.理解原型对象

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

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

    2.原型与IN操作符

    有两种方式使用in操作符,单独使用和在for-in循环里使用。单独使用时,

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

    同时使用hasOwnproperty()和In操作符,就可以确定该属性到底是存在于对象中,还是存在原型中。

    使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的(enumerabted)属性,其中包括存在于实例中的属性,也包括存在原型中的属性。屏蔽了原型中不可枚举属性的实例属性也会在for-in循环中返回,因为根据规定,所有开发人员定义的属性都是可枚举的(IE8除外)

    Object.keys()方法。接受一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

    结果中包含了不可枚举的constructor属性。Object.kyes()和Object.getOwnPropertyNames()方法都可以用来替代for-in循环。

    3.更简单的原型语法

    前面例子中每添加一个属性和方法都要敲一遍Person.prototype。为减少不必要的输入,也从视觉上更好的封装原型功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象。

    Function Person(){}

    Person.prototype = {

             name:”wang”,

             age:27,

             job:”web”,

             sayName:function(){

                       alert(this.name);

    }

    }

    在上面的代码中,Person.prototype设置为等于一个以对象字面量形式创建的新对象。最终结果相同,但有一个例外:constructor属性不再指向Person了。前面提到每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获得constructor属性。而使用以上的语话,本质上完全重写了默认的prototype对象。因此constructor属性也变成了新的对象constructor属性(指向Object构造函数)不再指向Person函数。 尽管instanceof操作符还能返回正确的结果。但通过constructor已经无法确定对象的类型了。如果constructor属性很重要,可以像下面这样特意将它设置为适当的值。

    Function Person(){}

    Person.prototype = {

             constructor:”Person”,

             name:”wang”,

             age:27,

             job:”web”,

             sayName:function(){

                       alert(this.name);

    }

    }

    以这种方式重设置 constructor属性会导致[[enumerable]]特性被设置为true,默认情况下,原生的constructor属性是不可枚举的。

    4.原型的动态性

    5.原生对象的原型

    原型模式的重要性不仅体现在创建自定义类型方面,就连所有原生的引用类型,都是采用这种模式创建的。所有原生引用类型(Array,Object,String等)都是其构造函数的原型上定义了方法。例如Array.prototype中可以找到sort()方法,string.prototype中可以找到substring方法。

    通过原生对象的原型,不仅可以取得所有的默认方法的引用,也可以定义新方法。

    6.原型对象的问题

    原型模式也不是没有缺点。

    首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。虽然这会在某种程度上带来一些不 方便,但这不是原型的最大问题。原型模式最大的问题是由其共享本性所导致的。

    原型中所有属性是被很多实例共享的,这种共享对函数非常合适。对于那些包含基本值的属性倒也说的过去,毕竟,通过在实例上添加一个同名属性,可以隐藏原型中的对应属性,然而,对于包含引用类型值的属性来说,问题就比较突出了。

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

    创建自定义类型的最常见的方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用。最大限度的节省了内存。

    这是用来定义引用类型的一种默认模式。

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

    6.2.5 寄生构造函数模式

    这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象,但从表面上看。这个函数又很像典型的构造函数。

    function creatPerson(name,age,job){

             var o = new Object();

             o.name = name;

             o.age = age;

             o.job = job;

             o.sayName = function(){

                       alert(this.name);

    return o;

    }

    6.3 继承

    ECMAScript只支持实现继承,而且其实现继承主要是依靠原型链实现的。

    6.3.1 原型链

    1.别忘记默认的原型

    所有引用类型默认都继承了Object,而这个继续也是通过原型链实现的。要记住,所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是自定义类型都会继续toString()valueOf()等默认方法的根本原因。

    2.确定原型和实例的关系

    可以通过两种方式来确定原型和实例之间的关系。

    第一种方式是通过instanceof操作符,只要用这个操作符测试实例与原型链中出现过的构造函数,结果就会返回true。

    Instance instanceof Object //true

    箱二种方式是使用isPrototypeOf()方法,同要,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此isPrototypeOf()方法也会返回true;

    Object.prototy.isPrototypeOf(instance)     //true;

    3.谨慎的定义方法

    子类型有时候需要重写超类型中的某个方法,或者需要添加超类型中不存在的某个方法。但不管怎样,给原型添加方法的代码一定要放在替换原型的语句之后。

    Function SuperType(){

    This.property = true;

    }

    SuperType.prototype.getSuperValue = function(){

    Return this.property;

    }

    Function subtype(){

    This.subproperty= false;

    }

    //继承了SuperType

    Subtype.prototype = new supertype();

     

    //添加新方法

    Subtype.prototype.getsubvalue = function(){

    Return this.subproperty;

    };

    //重写超类型中的方法

    Subtype.prototype.getsupervalue = function(){

    Return false;

    }

    Var instance = new Subtype();

    Instance.getSuperValue()    //false

    以上代码中,加粗的部分是两个方法的定义。

    还有一点,通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链。

    4.原型链的问题

    原型链虽然很强大,可以用它来实现继承,它也存在一些问题。

    最主要的问题来自包含引用类型值的原型。我们前面介绍过包含引用类型值的原型属性会被所有实例共享,而这也正是为什么要在构造函数中,而不是原型对象中定义属性的原因。在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。

    原型链的第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下给超类型的构造函数传递参数。。由于原型中包含引用类型值所带来的问题,实践中很少单独使用原型链。

    6.3.2 借用构造函数

    在解决原型中包含引用类型值所带来问题的过程中,开发人员开始使用一种叫做借用构造函数的技术(有时候也叫对象或经典继承)。这种技术的基本思想相当简单,即在子类型构造函数的内部调用超类型构造函数。别忘了,函数只不过是在特定环境中执行代码的对象,因为通过使用apply()和call()方法也可以在新创建的对象上执行构造函数。

    Function supertype(){

             This.colors = [“red”,”blue”,”green”];

    Function subtype(){

               //继承了supertype

               Supertype.call(this);

    }

    Var instance1 = new supertype();

    Instance1.colors.push(“black”);

    Instance1.colors      //”red,blue,green,black”

    Var instance2 = new subtype();

    Instance2.colors    //”red,blue,green”

    代码中加粗的那一行代码“借调”了超类型的构造函数,通过使用call方法(或apply()方法也可以),我们实际上在新创建的subtype实例的环境下调用了supertype构造函数。这样一来,就会在新的subtype上执行supertype()函数中定义的所有对象初始化代码。结果subtype的第个实例就都会具有自己的colors属性的副本了。

    1.传递参数

    function superType(name){this.name =name}

    function subType(){superType.call(this,"wang");this.age = 28;}

    var inst = new subType()

    在subtype构造函数内部调用supertype构造函数时,实际上是为subtype的实例设置了name属性。为了确保supertype构造函数不会重写子类型的性性。可以在超类型类型构造函数后,再添加应该在子类型中的定义属性。

    2.借用构造函数的问题

    如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数利用就无从谈起。而且在超类型的原型定义的方法,对子类型而方也是不可见的。结果所有类型都只能使用构造函数模式。考虑到这些问题,借用。

    6.3.3 组合继承

    组合继承,有时也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对属性的继承。

    组合继承避免了原型链借用构造函数的缺陷,整合了它们的优点,成为了javascript最常用的继承模式。而且,instanceof和isPrototypeOf()也能够用于识别基于组合继承创建的对象。

    6.3.4 原型式继承

    这种方法并没有使用严格意义上的构造函数。他的想法是借助原型可以基于已有对象创建新对象,同时还不必因此创建自定义类型。

    Function object(o){

    Function f(){}

    p.ptototype = o;

    return new f();

    }

    6.3.5 寄生式继承

    待补

  • 相关阅读:
    005 HTML+CSS(Class027
    004 HTML+CSS(Class024
    003 HTML+CSS(Class011
    002HTML+CSS(class007-010)
    001HTML+CSS(class001-006)
    021 vue路由vue-router
    020 Vue 脚手架CLI的使用
    019 Vue webpack的使用
    018 vue的watch属性
    017 vue的插槽的使用
  • 原文地址:https://www.cnblogs.com/maixi/p/4936509.html
Copyright © 2020-2023  润新知