zoukankan      html  css  js  c++  java
  • 面向对象之原型——challenge

    面向对象之原型

    object-oriented面向对象的设计,不同于其他语言,js中的面向对象没有类的概念,因此,其对象也有些特殊。

    所谓对象就是无序属性的集合,其属性可以包含基本值、对象、函数。也就是说对象是一组没有特定顺序的值的集合;对象的每个属性或方法都有自己名字,名字映射到一个值(值可以是数据或者函数)

    每个对象是基于引用类型创建的,是某个引用类型的实例。新对象就是new操作符后加一个构造函数创建的


    1理解对象

     这个就没什么必要,对象就是无序属性的集合,OK了

    2创建对象

    原始方式:传统的方式,只能单次创建,不能重复利用,代码重复率太高,效率低

    工厂模式:抽象了具体创建对象的过程,用函数来封装以特定接口创建对象的细节;虽然解决创建多个相似对象的问题,但是没有解决对象识别即怎么知道一个对象的类型问题

    构造函数:构造函数可以创建特定类型的对象比如object,array等等,也可以创建自定义的构造函数,从而定义自定义对象的类型和方法

             //普通的创建    只能单次创建,不能重复使用,大量重复代码,不合理
             var person1={
                   name:"double",
                   age:24,
                   sex:"man"
             }
             console.log(person1.name)         //double
    
             //工厂模式   抽取了创建具体对象的过程
             function createPerson(name,age,sex){
                   var obj=new Object();
                   obj.name=name;
                   obj.age=age;
                   obj.sex=sex;
                   obj.sayName=function(){
                          console.log(this.name)
                   }
                   return obj;                  //工厂模式下必须有return
             }
    
             var person1=createPerson("double",23,"man")
             var person2=createPerson("single",34,"woman")
    
             console.log(person1.name)       //double
    
              //构造函数模式  new+构造函数
             function Person(name,age,sex){
                    this.name=name;
                    this.age=age;
                    this.sex=sex;
                    this.sayName=function(){
                          console.log(this.name);
                    }
    //return this 可省略 } var person1=new Person("double",34,"man") var person2=new Person("single",32,"woman") console.log(person1.name) //double

    构造函数创建对象的不同

    1、没有显示的创建对象  2、直接将属性和方法赋给this对象  3、没有return语句

    注意:构造函数应该以大写字符开头,普通函数以小写字符开头。

    创建对象的过程

    1、创建一个新对象;2、将构造函数的作用域赋予给新对象;3、执行构造函数中的代码;4、返回新对象

    对象的constructor属性,指向对象的构造函数

             console.log(person1.constructor == Person)  //true  //对象都有一个constructor(构造函数)属性,true说明是指向Person
             console.log(person2.constructor == Person)
             
             //检测类型instanceof   创建的对象既是Object又是Person的实例
             console.log(person1 instanceof Object)          //true
             console.log(person1 instanceof Person)
             console.log(person2 instanceof Object)
             console.log(person2 instanceof Person)

    关于构造函数的补充

    构造函数与函数的区别在于调用的方式不同。任何函数只要通过new操作符调用就是构造函数

             构造函数与普通函数
             
             //构造函数
            var person3=new Person("nothing",35,"woman")
            console.log(person3.age)   //35
    
            //普通函数
            Person("world",34,"man")
            console.log(window.age)    //34      直接调用,那就跑到全局作用域上,添加到window
       
            //在另一个对象的作用域调用
            var  obj=new Object()
            Person.call(obj,"double",45,"man")
            console.log(obj.name)       //double

    构造函数的问题

    每个方法都要在每个实例上重新创建一遍;js中函数本身就是对象,所以每个实例上的每个新的方法,就是新的对象;不同实例上的同名函数是不相等的

            function Person(name,age,sex){
                  this.name=name;
                  this.age=age;
                  this.sex=sex;
                  this.sayName=function(){
                          return this.name
                  }
            }
    
            var person1=new Person("double",34,"man")
            var person2=new Person("single",45,"woman")
    
            console.log(person1.sayName==person2.sayName)    //false   不是相同的函数,而是分别创建了
            console.log(person1.name.prototype==person2.name.prototype)       //true   指向同一原型属性
            console.log(person1.sayName.prototype==person2.sayName.prototype)   //false  指向不同原型属性
    
            //创建两个完成相同任务的Function实例确实没必要,况且有Function存在,不需要将函数绑定到特定对象上去
            function Person(name,age,sex){
                  this.name=name;
                  this.age=age;
                  this.sex=sex;
                  this.sayName=sayName;
            }
    
            function sayName(){      //提出来是可以的,person1和person2指向全局作用域上的定义的同一函数,一旦函数的方法多了就麻烦啦
                return this.name
            }
            //一句话结束:原先是创建对象的方法来创建函数,每个person中都包含不同Function实例,会导致不同的作用域链和标识符解析;现在是声明函数的方法创建函数

    原型模式

    每个引用类型(Array Object Function)都有prototype(原型)属性,是个指针,指向一个对象,这个对象就是包含可以由特定类型的所有实例共享的属性和方法(即原型对象);

    所有的引用类型都在其构造函数的原型上定义了方法,Array.prototype可以找到sort方法,在String.prototype可以找到substring方法;

    好处:让所有对象实例共享它包含的属性和方法

            function Person(){
            }
              
            Person.prototype.name="double"
            Person.prototype.age=23
            Person.prototype.sex="man"
            Person.prototype.sayName=function(){
                  console.log(this.name)
            }
    
            var person1=new Person()
            person1.sayName()         //double
    
            var person2=new Person()
            person2.sayName()         //double
            
            console.log(person1.sayName==person2.sayName)   //true

    1、理解原型模式

    prototype属性:每一个新创建的函数,都有一个prototype属性,说过了,是个指针,指向它的原型对象(Person.prototype);

    constructor属性:所有的原型对象有个constructor属性,也是个指针,指向prototype所在的函数。即Person.prototype.constructor指向的就是Person这个构造函数。

    _proto_属性:创建构造函数后,其原型对象默认只会取得constructor属性,其他方法会由object继承。当调用构造函数创建一个新实例后,该实例内部有个_proto_属性,也是指针,指向原型对象

    注意:这个属性对脚本是不可见的,称为隐式属性;这个连接存在于实例和原型对象之间,不是存在实例与构造函数之间,来看此图就知道了

    isPrototypeOf()方法:这是原型对象的一个方法,用来判断某个实例与原型对象是否存在_proto_联系

       console.log(Person.prototype.isPrototypeOf(person1))     //true
       console.log(Person.prototype.isPrototypeOf(person2))     //true

    Object.getPrototypeOf()方法:ES5新增的方法,返回原型对象

            console.log(Object.getPrototypeOf(person1) == Person.prototype)    //true
            console.log(Object.getPrototypeOf(person1).name)         //double   

    注意:每当代码读取某个对象的某个属性,都会执行一次搜索。首先搜索该实例,没有就搜索原型对象,再没有就搜索Object原型对象;一旦找到就返回,不会继续搜索,也就是说相同名字的属性就会被遮蔽,而不是替代或者消失,是遮蔽。当删除属性时就会继续查找

    hasOwnePrototype()方法:用来检测一个属性是存在实例中还是原型中(属性存在实例中返回true)

            console.log(person1.hasOwnProperty("name"))      //false    根据上面的则是在原型中的
            console.log(person1.hasOwnProperty("age"))       //false

    2、原型和in操作符

    in操作符可以用两种方式,一个是单独使用,一个是for in 循环中,来吧

    单独使用:通过对象能够访问特定属性,不管存在于实例还是原型中都返回true

            function Person(){
            }
    
            Person.prototype.name="double"
            Person.prototype.age=23
            Person.prototype.sex="man"
    
            var person1=new Person()
            var person2=new Person()
    
            console.log(person1.hasOwnProperty("name"))     //false    存在原型中
            console.log("name" in person1)                  //true
    
            person1.name="single"
            console.log(person1.hasOwnProperty("name"))     //true     存在实例中,这里实例的name只是遮盖了原型中的name
            console.log("name" in person1)                  //true
            
    //可以通过Object.hasOwnProperty()和操作符in来判断到底是存在于实例还是原型中 function hasPrototypeProperty(object,name){ return !object.hasOwnProperty(name)&&(name in object) } console.log(hasPrototypeProperty(person1,"name")) //false 说明存在实例中 console.log(hasPrototypeProperty(person2,"name")) //true 说明存在原型中

    for in 使用:返回的是所有能够通过对象访问的,可枚举的(enmuerated)属性,既包括实例中又包括原型中,遮蔽的原型中(不可枚举的)的实例属性也会被循环返回;但是这不是我们想要的结果,我们只想要实例中可枚举的属性,可以用ES5中Object.keys()的方法:接受一个对象作为参数,返回一个包含所有可枚举属性的字符串数组

            function Person(){
            }
    
            Person.prototype.name="double"
            Person.prototype.age=23
            Person.prototype.sex="man"
            Person.prototype.sayName=function(){
                   return this.name
            }
    
            var person1=new Person()
            var person2=new Person()
            
            var keys=Object.keys(Person.prototype)
            console.log(keys)              //返回的是字符串数组,所有可枚举的属性["name","age","sex","sayName"]
            
            var keyss=Object.getOwnPropertyNames(Person.prototype)
            console.log(keyss)             //返回的是所有实例属性,含有不可枚举的属性["constructor","name","age","sex","sayName"]

    所以呢,直接用字面量原型吧,简洁明,大方得体;注意了:因为将原型设置为字面量形式,所以呢,它现在变为一个新对象了,结果是相同的,但是呢,这里的constructor不再指向Person了

    为啥呢?

    每创建一个对象,此对象就会有他的原型对象(原型对象会自动得到constructor),这里重写了原型对象,所以呢constructor就变成新原型对象的constructor属性,指向的是Object构造函数

            function Person(){
            }
    
            Person.prototype={                     字面量表示
    constructor:Person, 当constructor very important时,那就作为返回值带上呗 name :
    "double", age : 12, sex : "woman", sayName : function(){ return this.name } } var person1=new Person() console.log(person1.name) //double

    3、原型的动态性

    正如前面所说的,当查找某一个属性的时候,会进行层层的搜索,首先在实例层中,然后在原型层,再次在Object中。

    注意:原型的查找是一次搜索的,对原型的任意修改都会立即从实例上反映出来(即使是先创建实例后修改原型)

            var person1=new Person()
            Person.prototype.sayName=function(){
                  alert(this.age)
            }
            
            person1.sayName()   //12

    所以呢,可以通过对原型添加、修改某些属性达到效果,但是当实例创建了,重写原型gg了,因为实例和原型间的_prototy_是个指针,不是副本,重写原型就切断了原先的联系,会报错的。

    基于前面都是自定义对象,那么对于原生的引用类型呢,是不是可以对原型自定义的添加属性呢?

    当然可以啦,但是不推荐做,原因嘛,你懂的。

            //为String原型添加个方法
            String.prototype.startWidth=function(text){  
                   return this.indexOf(text)==0
            }
            var message="Hello world"
            console.log(message.startWidth("Hello"))    //true

    4、原型模式的问题

    从上面可以很容易的看出来,共享性是原型模式的最大特色,但是它是一把双刃剑,对于函数方法十分有效,游刃有余;可是对于一些基本属性,就没办法相构造函数那样解决(当然我们可以在后续添加,遮盖原型中的属性);最重要的是对于包含引用类型值得属性就是个大麻烦,比如数组,来看例子吧!

           function Person(){
           }
           Person.prototype={
                   constructor:Person,
                   name:"double",
                   age:23,
                   sex:"woman",
                   friends:["single","mike"]
           }
           var person1=new Person()
           var person2=new Person()
    
           person1.friends.push("tom","jack")
    
           console.log(person1.friends==person2.friends)   //true   因为被共享了  

    所以各位,这种原型模式你们会用吗?

    那么有什么好的办法呢?当然是有的,来瞧一瞧看一看,组合使用构造函数模式和原型模式就可以了(也是使用的最多的)

           function Person(name,age,sex){
                   this.name=name;
                   this.age=age;
                   this.sex=sex;
           }
           Person.prototype={
                  constructor:Person,
                  sayName:function(){
                          console.log(this.name)
                  }
           }
    
           var person1=new Person("double",23,"man")
           var person2=new Person("single",34,"woman")
    
           console.lgo(person11.name===person2.name)       //false 基本值不一样
           console.log(person1.sayName===person2.sayName)  //true  方法一样

    有些奇怪吗?确实,为啥好端端的函数时,将原型和构造函数分开,来吧,让封装函数让你享受封装的魅力所在。我们称之为动态原型模式(也就是比较完美了)

           function Person(name,age,sex){
                   this.name=name;
                   this.age=age;
                   this.sex=sex;
    
                   if(typeof this.sayName != "function"){                  //当sayName方法不存在时,添加到原型中,初次调用时执行,此后原型已经初始化了,后续更改也会在实例中实时反映的
                        Person.prototype.sayName=function(){               
                               alert(this.name)
                        };
                   }
           }
    
           var friends=new Person("double",34,"man")
           friends.sayName()      //double

    于此,原型模式讲的也就差不多了,补充一下,事实上还有原型链中还有Object,所有函数(构造和普通函数)默认的原型都是Object的实例,所以呢,默认原型都会包含一个内部指针_proto_,指向Object.prototype

    所以呢,自定义类型都会继承toString,valueOf默认方法的根本原因。结合上面,看一下这个原型链的图吧

    还有两种模式,不常用,了解一下吧

           寄生构造函数模式:函数仅仅是封装创建对象的代码,然后返回新创建的对象
           返回对象与构造函数与构造函数的原型属性没关系,说白了,就是个普通的新对象
           在特殊的情况下为对象创建构造函数
           function Person(name,age,sex){
                   var o=new Object();
                   o.name=name;
                   o.age=age;
                   o.sex=sex;
                   o.sayName=function(){
                         alert(this.name)
                   };
                   return o
           }
           var person=new Person("double",34,"man")    //只比工厂模式多个new
           person.sayName()
    
           稳妥构造函数模式
           没有公共属性,方法也不引用this对象
           在一些安全的环境下(禁用this和new)或者在防止数据被其他应用程序改动时使用
           function Person(name,age,sex){
                   var o=new Object()
                   o.sayName=function(){
                       alert(name)
                   }
                return o
           }
           var friend=Person("double",34,"man")
           friend.sayName()                     //true

    好吧,写了这么多,关注一下呗,后续再更继承的知识

    如果你关注我

    我就……(你懂的)

  • 相关阅读:
    三数之和
    罗马数字与整数
    Oracle 开启或关闭归档
    Oracle RMAN scripts to delete archivelog
    Oracle check TBS usage
    Oracle kill locked sessions
    场景9 深入RAC运行原理
    场景7 Data Guard
    场景4 Data Warehouse Management 数据仓库
    场景5 Performance Management
  • 原文地址:https://www.cnblogs.com/iDouble/p/8401058.html
Copyright © 2011-2022 走看看