zoukankan      html  css  js  c++  java
  • JavaScript基础笔记(四) JS式面向对象

    JS式面向对象

    一、理解对象

    一)属性类型

    ECMA-262 5 版在定义只有内部才用的特性(attribute)时,描述了属性(property)的各种特征。

    ECMA-262 定义这些特性是为了实现 JavaScript 引擎用的,因此在 JavaScript 中不能直接访问它们。 
    1.数据属性

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

    [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性能否修改属性的特
    性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的
    这个特性默认值为 true
    [[Enumerable]]:表示能否通过 for-in 循环返回属性。像前面例子中那样直接在对象上定
    义的属性,它们的这个特性默认值为 true
    [[Writable]]:表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的
    这个特性默认值为 true
    [[Value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,
    把新值保存在这个位置。这个特性的默认值为 undefined

    直接在对象上定义的属性,它们的[[Configurable]][[Enumerable]]
    [[Writable]]特性都被设置为 true,而[[Value]]特性被设置为指定的值。

            var p = {
                name: "Tom",  //该属性的[[Value]]特性被设置为"Tom"
                age: 1
            }

    要修改属性的默认特性,必须使用ECMAScript5的Object.defineProperty()方法

    该方法的三个参数:属性所在对象、属性的名字、描述符对象,其中,描述符(descriptor)对象的属
    性必须是: configurableenumerablewritable value

            var person = {
                name: "Tang",
                gender: "man"
            }
    
            Object.defineProperty(person,"name",{
                writable: false,
                value: "Salt Fish"
            });
    
            console.log(person.name);  //Salt Fish
            person.name = "King";
            console.log(person.name);  //Salt Fish
            var person = {name:null};
    
            Object.defineProperty(person,"name",{
                configurable: false,
                value: "King"
            });
    
            //以下语句抛出错误:can't redefine non-configurable property "name"
            // 一旦把属性定义为不可配置的,
            //就不能再把它变回可配置了。此时,再调用 Object.defineProperty()
            //方法修改除 writable,value 之外的特性,都会导致错误
            /*Object.defineProperty(person,"name",{
                configurable: true,
                value: "Fish"
            });*/
    
            //没问题
            Object.defineProperty(person,"name",{
                value: "Salt Fish"
            });
    
            console.log(person.name);  //Salt Fish

    此外,在调用 Object.defineProperty()方法时,如果不指定, configurableenumerable
    writable 特性的默认值都是 false

    2.访问器属性

    访问器属性不包含数据值;它们包含一对儿 getter setter 函数(不过,这两个函数都不是必需的)。
    在读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值;在写入访问器属性时,会调用
    setter 函数并传入新值,这个函数负责决定如何处理数据。

    var book = {
                _year: 1990,  //year前面的"_"记号表示只能通过对象方法访问的属性
                edition: 1
            };
    
            Object.defineProperty(book,"year",{
                get: function () {
                    return this._year; //有"_"
                },
    
                set: function (v) {
                   if (v > 2008) {
                       this._year = v;
                       this.edition += v - 2008;
                   }
                }
            });
    
            book.year = 2012;  //没有"_"
            console.log("Year: "+book.year+"
    "+"Edition: "+book.edition);
            /*Year: 2012
            Edition: 5*/

    注意:如果只指定getter意味着属性是不可写的,只指定setter表示属性不可读。

    Object.definedProperties()方法:通过该方法可以一次定义多个属性。

        var book = {};
        Object.defineProperties(book, {
            _year: {
                value: 2004
            },
            edition: {
                value: 1
            },
            year: {
                get: function(){
                    return this._year;
                },
                set: function(newValue){
                    if (newValue > 2004) {
                        this._year = newValue;
                        this.edition += newValue - 2004;
                    }
                }
            }
        });

    Object.getOwnPropertyDescriptor()方法 :可以取得给定属性的描述
    符。这个方法接收两个参数:属性所在的对象和要读取其描述符的属性名称。返回值是一个对象,如果
    是访问器属性,这个对象的属性有 configurableenumerableget set;如果是数据属性,这
    个对象的属性有 configurableenumerablewritable value

            var book = {
                _year: 1990,  //year前面的"_"记号表示只能通过对象方法访问的属性
                edition: 1
            };
    
            Object.defineProperty(book,"year",{
                get: function () {
                    return this._year; //有"_"
                },
    
                set: function (v) {
                   if (v > 2008) {
                       this._year = v;
                       this.edition += v - 2008;
                   }
                }
            });
    
            book.year = 2012;  //没有"_"
    
            var desc = Object.getOwnPropertyDescriptor(book,"year");
            console.log(desc.value);  //undefined
            console.log(desc.configurable);  //false
    
            var desc2 = Object.getOwnPropertyDescriptor(book,"_year");
            console.log(desc2.value);  //2012
            console.log(desc2.configurable);  //true

    二、创建对象

    关于new 

        function Person() {}
    
        Person.prototype.name = "Tom";
        var p1 = Person();
        var p2 = new Person();
    
        /*console.log(p1.name);*/ //undefined
        console.log(p2.name); //Tom
        /*
        * 使用new操作符将函数作为构造器进行调用的时候,其上下文被定义为新对象实例。
        * */
        function Man() {
            this.age = 22;
            this.position = "teacher";
            this.say = function () {
                console.log("Big sb!");
            }
        }
        Man.prototype.age = 0;
        var m1 = new Man();
        //注意优先级
        console.log(m1.age); //22
    var m = new Man();
        //每个对象实例都有一个constructor属性,该属性引用的是创建该对象的构造器
        //而prototype则是构造器的一个属性。所以每个实例都可以找到自己的原型。
        console.log(m.constructor.prototype.age); //0
        console.log(Man.prototype.age);//0
        console.log(Man.constructor.prototype.age); //undefined
        console.log(m.constructor.prototype.constructor.prototype.age); //0
    console.log(m.constructor === Man); //true
    var m2 = new m.constructor();
    console.log(m === m2); //false

    一)工厂模式

            function createPerson(name, gender, job) {
                var instance = new Object();
                instance.name = name;
                instance.gender = gender;
                instance.job = job;
    
                instance.introduce = function () {
                    console.log(this.name);
                }
                return instance;
            }
    
            var p = createPerson("ashin","man","killer");
            p.introduce();

    工厂模式虽然解决了创建多个相似对象的问题,但并没有解决对象识别问题。

    二)构造函数模式

    创建自定义构造函数,从而定义自定义对象类型的属性和方法。

            //自定义一个函数
            function Person(name, gender, job) {
                this.name = name;
                this.gender = gender;
                this.job = job;
    
                this.introduce = function () {
                    console.log("My name is "+this.name);
                }
            }
    
            //用new操作符执行该函数(作为构造函数执行)。
            var p = new Person("Jerry","boy","actor");
            p.introduce(); //My name is Jerry
            console.log(p.constructor == Person); //true
            console.log(p instanceof Person);     //true
            console.log(p instanceof Object);     //true
    
            //当做普通函数使用
            var p2 = Person("Jax","man","Gui");
            window.introduce(); //My name is Jax
            // console.log(p2.constructor == Person); //报错
            console.log(p2 instanceof Person); //false 这里p2是执行结果,当然是false
            console.log(p2 instanceof Object); //false 同上

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

    var name = "Window";
        function Person(name) {
            this.name = name;
        }
    
        var tom = new Person("Tom")
        console.log(this.name); //Window
        console.log(tom.name);  //Tom
    
        Person("Jack");
        console.log(this.name);  //Jack

    使用构造函数模式解决了对象识别问题,此外这种方法定义的构造函数是定义在Global对象中的。

    使用构造函数主要问题:对象内部的每个方法都要在每个实例上重新创造一遍。创建两个完成同样任务的 Function 实例的确没有必要

    解决方法一,把函数定义转移到构造函数外部。

           function Person(name, age, job) {
                this.name = name;
                this.age = age;
                this.job = job;
    
                this.sayName = sayName;
            }
    
            function sayName() {
                console.log("Name:" +this.name);
            }

    新问题:破坏自定义引用类型的封装性。

    三)原型模式

    理论依据:

    无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype
    属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个 constructor
    (构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。创建了自定义的构造函数之后,、

    其原型对象默认只会取得 constructor 属性;至于其他方法,则

    都是从 Object 继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部
    属性),指向构造函数的原型对象

            function Person(name, age, job) {
            }
    
            Person.prototype.name = "Nicholas";
            Person.prototype.age = 29;
            Person.prototype.job = "Software Engineer";
            Person.prototype.sayName = function () {
                console.log(this.name);
            }
    
            var person1 = new Person();
            var person2 = new Person();
    
            person1.sayName();  //Nicholas
            person2.sayName();  //Nicholas
            console.log(person1.sayName === person2.sayName); //true

     每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先

    从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,
    则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这
    个属性,则返回该属性的值。也就是说,在我们调用 person1.sayName()的时候,会先后执行两次搜
    索。首先,解析器会问:
    “实例 person1 sayName 属性吗?”答:“没有。”然后,它继续搜索,再
    问:“person1 的原型有 sayName 属性吗?”答:“有。”于是,它就读取那个保存在原型对象中的函
    数。当我们调用 person2.sayName()时,将会重现相同的搜索过程,得到相同的结果。而这正是多个
    对象实例共享原型所保存的属性和方法的基本原理。
    虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果我们
    在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该
    属性将会屏蔽原型中的那个属性
    但通过delete操作符可以删除属性,取消屏蔽

    function Person(){
    }
    Person.prototype.name = "Nicholas";
    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 = "Greg";
    alert(person1.name); //"Greg"—— 来自实例
    alert(person2.name); //"Nicholas"—— 来自原型
    delete person1.name;
    alert(person1.name); //"Nicholas"—— 来自原型

    in 操作符会在通过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原型中 
    同时使用 hasOwnProperty()方法和 in 操作符,就可以确定该属性到底是存在于对象中,还是存在于
    原型中

    要取得对象上所有可枚举的实例属性,可以使用 ECMAScript 5 Object.keys()方法。这个方法
    接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组

    更简单的原型语法:

    function Person(){
        }
        Person.prototype = {
            name : "Nicholas",
            age : 29,
            job: "Software Engineer",
            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 : "Nicholas",
            age : 29,
            job: "Software Engineer",
            sayName : function () {
                alert(this.name);
            }
        };

    注意,以这种方式重设 constructor 属性会导致它的[[Enumerable]]特性被设置为 true

     原生对象的原型:

    原型模式的重要性不仅体现在创建自定义类型方面,就连所有原生的引用类型,都是采用这种模式
    创建的。所有原生引用类型(ObjectArrayString,等等)都在其构造函数的原型上定义了方法。

     原型对象的问题:

       function Person(){
        }
        Person.prototype = {
            constructor: Person,
            name : "Nicholas",
            age : 29,
            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); //"Shelby,Court,Van"
        alert(person2.friends); //"Shelby,Court,Van"
        alert(person1.friends === person2.friends); //true

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

    创建自定义类型最常用的方式就是,组合使用构造函数模式与原型模式。

        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,Count,Van"
        alert(person2.friends); //"Shelby,Count"
        alert(person1.friends === person2.friends); //false
        alert(person1.sayName === person2.sayName); //true

    五)动态原型模式

            function Person(name, age, job) {
                this.name = name;
                this.age = age;
                this.job = job;
            }
    
            //这就是所谓的动态创建......
            if (typeof this.sayName != "function") {
                Person.prototype.sayName = function () {
                    console.log(this.name);
                }
            }

    六)稳妥构造函数模式

            //没用公共属性,方法也不引用this
            function Person(name, age, job) {
                var obj = new Object();
                obj.sayName = function () {
                    console.log(name);
                }
                return obj;
            }
    
            var p = Person("Tom");
            p.sayName();  //Tom

    三、继承

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

            function SuperType() {
                this.property = true;
            }
    
            SuperType.prototype.getSuperValue = function () {
                return this.property;
            }
            
            function SubType() {
                this.subproperty = false;
            }
    
            //继承
            SubType.prototype = new SuperType();
            SubType.prototype.getSubValue = function () {
                return this.subproperty;
            }
    
            var instance = new SubType();
            console.log(instance.getSuperValue()); //true
            console.log(instance.getSubValue());   //false

    组合继承:

    组合继承是最常用的继承方法

            function SuperType(name) {
                this.name = name;
                this.colors = ["blue","red","yellow"];
            }
    
            SuperType.prototype.sayName = function () {
                console.log("My name is "+this.name);
            }
    
            function SubType(name, age) {
                SuperType.call(this,name);
                this.age = age;
            }
    
            //继承方法
            SubType.prototype = new SuperType();
            SubType.prototype.constructor = SubType;
            SubType.prototype.sayAge = function () {
                console.log("I'm "+this.age+" years old.");
            }
    
            var instance = new SubType("Kotlin",3);
            instance.colors.push("black");
            console.log(instance.colors);  //["blue","red","yellow","black"]
            instance.sayName(); //My name is Kotlin
            instance.sayAge();  //I'm 3 years old.
    
            var p = new SubType("Java",20);
            console.log(p.colors); //["blue","red","yellow"]
            p.sayName();  //My name is Java
            p.sayAge();   //I'm 20 years old.
    Simple is important!
  • 相关阅读:
    mormot2封装tcp
    mormot.net.client.pas
    mormot.net.server.pas
    delphi working with big data databases in Cassandra, Couchbase and MongoDB
    Grijjy.ProtocolBuffers.pas
    Grijjy.Bson.Serialization.pas
    GrijjyFoundation开源框架
    Unidac内存表使用
    传统关系型数据库正在完成华丽的蜕变
    使用arthas 分析java 应用的一个流程总结
  • 原文地址:https://www.cnblogs.com/Shadowplay/p/8392965.html
Copyright © 2011-2022 走看看