javascript高级程序设计(第六章面向对象的程序设计)

本文深入探讨JavaScript中的对象创建、属性管理、构造函数、原型模式及其组合应用,详解各种继承模式的特点与实现。
  1. 没有类的概念
  2. 定义类的方式变化

    var person = new Object();
    person.name = "marain";
    person.age = 24;
    person.job="software engineer";
    
    person.sayName=function(){
        alert(this.name)
    }
    
    
    //后来使用对象字面量
    var person= {
        name:"marain",
        age:24,
        job : "software engineer",
        sayName:function(){
            alert(this.name)
        }
    }
    
  3. 属性类型(数据属性和访问器属性)
    数据属性

1 数据属性有4个描述其行为的特性
[[configurable]] : 能否通过delete 删除属性,从而重新定义属性,修改属性的特性,或者能否把属性修改为访问属性。默认值 true
2 [[enumerable]] 通过for-in 循环返回属性 默认 true
3 [[writable]] 是否可以修改属性值 默认 true
4 [[value]] 包含这个属性的属性值

Object.defineProperty(person,'name',{
    weitable:false, //不可以更改
    value:'marain'
})

 alert(person.name); //marain
 person.name= "test";  //严格模式下报错
 alert(person.name); //marain



Object.defineProperty(person,'name',{
    configurable:false, //不可以更改
    value:'marain'
})

 alert(person.name); //marain
 delete person.name;  //严格模式下报错
 alert(person.name); //marain

而且这个属性再也不能变成可配置的了  设置不可配置的过程不可逆

访问器属性
1 [[configurable]] : 能否通过delete 删除属性,从而重新定义属性,修改属性的特性,或者能否把属性修改为访问属性。默认值 true
2 [[enumerable]] 通过for-in 循环返回属性 默认 true
3 [[get]] 在读取属性是调用的函数 默认undefined
4 [[set]] 在写入属性是调用的函数 默认undefined

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

})

book.year = 2005l
alert(book.edition);

同时定义多个属性

    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() 取得给定属性的描述符 方法接受将个参数:属性所在的对象和读取器描述符的属性名称,返回一个对象

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;
            }
        }
    }
})

var descriptor =Object.getOwnPropertyDescriptor(book,"_year"); //数据属性
console.log(descriptor);
alert(descriptor.value);
alert(descriptor.configurable);
alert(typeof descriptor.get);

var descriptor =Object.getOwnPropertyDescriptor(book,"year"); //访问器属性
console.log(descriptor);
alert(descriptor.value);
alert(descriptor.enumerable);
alert(typeof descriptor.get);

创建对象

虽然构造函数和对象字面量可以创建单个对像,但是缺点是使用同一个接口创建很多对象,会产生大量重复代码,人们就开始使用工厂模式的一种变体

工厂模式 – 抽象创建具体对象的过程

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('marain1',24,"software engineer");
var person2 =   createPerson('marain2',24,"software engineer");

缺点:没有决绝对象识别的问题 (怎样知道一个对象的类型)—–没有理解

构造函数模式

  function Person(name,age,job){

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

   }

   var person1 =  new Person('marain1',24,"software engineer");
   var person2 =  new Person('marain2',24,"software engineer");

要创建一个新对象Person实例,经历:
1 创建一个新对象
2 将构造函数中的作用域赋给新对象 this指向新对象
3 执行狗仔函数中国的代码(为新对象添加属性)
4 返回新对象

构造函数的问题

person1 和 person2 都有一个名叫sayName()的方法 但两个方法不是同一个function的实例,因为 函数也是对象,因此每定义一个函数,也就是实例化一个对象。

function Person(name,age,job){

    this.name  =   name;
    this.age   =   age;
    this.job   =   job;
    this.sayName   =  new Function(){
        alert(this.name);
    }  // 与生命函数在逻辑上是等价的

}

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

有一个弥补的办法

function Person(name,age,job){

    this.name  =   name;
    this.age   =   age;
    this.job   =   job;
    this.sayName   = sayName;
}
function sayName(){
        alert(this.name);
}  // 与生命函数在逻辑上是等价的

alert(person1.sayName == person2.sayName  ) // true

带来的新问题是 定义一个构造函数的方法就要在 全局里面定义一个函数吗???

==》 原型模式

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

function Person(){

 }
 Person.prototype.name = "marain";
 Person.prototype.age = 24;
 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  )

怎么理解原型对象呢?

创建新对象就会按照特定的规则 为这个对象创建一个prototype的属性,这个属性指向函数的原型对象,在默认情况下所有的原型对象都会自动获得一个constructor (构造函数属性)
,这个属性包含一个指向protocotype 属性所有的指针。 Person。prototype.constructor 指向 Person

当调用构造函数创造新的实例以后,该实例的内部将包含一个指针,指向构造函数的原型对象这个指 叫做[[Prototype]] 必须明确的事,这个连接时存在在实例和构造对象原型之间的,而不是实例和构造函数之间的 ( 相当于 构造函数只是一个代孕的妈妈 真正提供受精卵的 是构造对象的原型对象,原型对象里面有一个属性 constructor 是指的代孕妈妈是谁 代孕妈妈的prototype里面说的是 受精卵是从哪来的(原型) 生下来的儿子呢 像的是 受精卵(原型) 和 这个代孕妈妈 没有关系)

这里写图片描述

虽然在所有的实现中都无法访问到[[Prototype]] 但是可以通过 isPrototypeOf() 方法来判断对象之间时候存在这种关系

Person.prototype.isPrototypeOf(person1) //true

Object.getPrototypeOf(person1) == Person.prototype // true
getPrototypeOf() 可以获取这个对象的原型对象

function Person(){

 }
 Person.prototype.name = "marain";
 Person.prototype.age = 24;
 Person.prototype.job = "software engineer";
Person.prototype.sayName = function() {
    alert(this.name);
};

var person1 = new Person();

var person2 = new Person();

person1.name ="marain222";
person1.name  //marain222
person2.name  //marain

对象在查看自己属性和方法的时候 都会现在实例里面找 然后去原型里面找 实例里面有 就会屏蔽原型里面的值 但是如果delete 实例属性 那么会继续读取原型里面的值

hasOwnProperty() //检测一个属性是否存在于实例中,还是原型中 属性在对象实例中时 返回 true

这里写代码片

这里写图片描述

Object.getOwnPropertyDescriptor() 只能用于实例属性,如果想要获去原型属性的描述符 ,必须在原型对像上调用 Object.getOwnPropertyDescriptor()

原型于in操作符

in 操作符有两种使用方式, 单独使用 和for-in 使用、

单独使用:
in操作符会在通过对象能够访问给定属性时返回true, 无论属性是否存在原型还是实例

function Person(){

 }
 Person.prototype.name = "marain";
 Person.prototype.age = 24;
 Person.prototype.job = "software engineer";
Person.prototype.sayName = function() {
    alert(this.name);
};

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


alert(person1.hasOwnProperty('name')) //false 
alert('name' in person1);   //true


person1.name="haha";
alert(person1.name);
alert(person1.hasOwnProperty('name')) //true 
alert('name' in person1);   //true

hasOwnProperty() 只有实例中有 in 全部都有 ,如果实例里面没有 in里面有 就说明在原型里面

for-in

 function Person(){
 }
 Person.prototype.name = "marain";
 Person.prototype.age = 24;
 Person.prototype.job = "software engineer";
Person.prototype.sayName = function() {
    alert(this.name);
};
var person1 = new Person();
for( x in person1){
    console.log(person1.x) //undefined
}   

var key =Object.keys(Person.prototype);
console.log(key);    //(4) ["name", "age", "job", "sayName"]


person1.name="123";
person1.age=123;
var keys =Object.keys(person1);

var key2=Object.getOwnPropertyNames(person1); 

var key3=Object.getOwnPropertyNames(Person.prototype);
console.log(key2);    //(2) ["name", "age"]
console.log(key3);    //(5) ["constructor", "name", "age", "job", "sayName"]

更简单的原型

/*Person.prototype={
    name: "marain",
    age: 24,
    job: "software engineer",
    sayName: function() {
        alert(this.name);
    },
    constructor: Person,//会变成可枚举的属性
  }*/


 Person.prototype={
    name: "marain",
    age: 24,
    job: "software engineer",
    sayName: function() {
        alert(this.name);
    },
    //constructor: Person,//会变成可枚举的属性
  }
  Object.defineProperty(Person.prototype,"constructor",{
    enumerable:false,
    value:Person
  });

  var person222= new Person();
  console.log(person222);

原型的动态性

重写原型

var friend = new Person();

person.prototype.sayHi = function (){
    alert('hi');
}

feiden.sayHi()  这是没问题的


function  Person(){

}

var friend = new Person();

Person.prototype = {
       name: "marain",
    age: 24,
    job: "software engineer",
    sayName: function() {
        alert(this.name);
    },
    constructor: Person,//会变成可枚举的属性 
}

friend.sayName() // 不可以

原因:

这里写图片描述

原型模式的问题

1.省略了构造函数穿参的阶段,因此原型里面的数字是固定的。这样很不方便
2.如果修改了原型里面的值,另外的实例都会受到影响。

这里写图片描述

构造函数模式和原型模式的结合

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

 var person1 = new Person('marain',24,"software engineer");

继承

js 继承主要依赖原型链

一个构造方法的原型对象是另一个类型的实例


    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;
    }

    var instance = new SubType();

    alert(instance.getSuperValue());


这里写图片描述

加上object的原型

这里写图片描述

确认原型和实例的关系

instanceof 和  isPropertyOf()


console.log(instance  instanceod Object) //true;
console.log(instance  instanceod SuperType) //true;
console.log(instance  instanceod SubType) //true;


console.log(Object.prototype.isPropertyOf(instance)) //true;
console.log(SuperType.prototype.isPropertyOf(instance)) //true;
console.log(SubType.prototype.isPropertyOf(instance)) //true;

给原型添加方法的代码一定要放在替换原型的语句之后

这里写图片描述

通过原型链实现继承时,不能使用对象字面量创建原型,这样会重写原型链

这里写图片描述

原型链的问题

问题1:
包含类型值的 原型也会被实例共享

问题2:
不能在不影响所有对象实例的情况下,给超类的构造函数传递参数

解决办法:
借用构造函数

function SuperType(){
    this.colors=["red","blue","green"];
}


function Subtype(){
    SuperType.call(this);//这里继承supertype
}


var instancel = new SubType();
instancel.colors.push("black");
console.log(instancel.colors);

var instancel2 = new SubType();
console.log(instancel2.colors);

子类给超类传参数

function SuperType(name){
    this.name=name;
}


function SubType(){
    SuperType.call(this,"marain");//这里继承supertype
    //实例属性
    this.age = 29;
}

var instancel = new SubType();
console.log(instancel.name);//marain
console.log(instancel.age);// 29

组合继承
将原型链 和借用构造函数的技术组合在一起

function SuperType(name){
    this.name=name;
    this.colors=['red','blue','green'];
}


SuperType.prototype.sayName=function(){
    alert(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(){
    alert(this.age);
};

var instance1 = new SubType("Nicholas", 29);

instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29

var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

这是最常用的继承模型

原型式继承

var person = {
name: "Nicholas",
     friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
    anotherPerson.name = "Greg";
    anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
    yetAnotherPerson.name = "Linda";
    yetAnotherPerson.friends.push("Barbie");
    alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"

寄生式继承

function createAnother(original){
    var clone = object(original); //通过调用函数创建一个新对象
    clone.sayHi = function(){ //以某种方式来增强这个对象
        alert("hi");
    };
    return clone; //返回这个对象
}

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一
点与构造函数模式类似。

寄生组合式继承

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    SuperType.call(this, name); //第二次调用 SuperType()
    this.age = age;
}
SubType.prototype = new SuperType(); //第一次调用 SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
};

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背
后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型
原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型
的原型

function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype); //创建对象
    prototype.constructor = subType; //增强对象
    subType.prototype = prototype; //指定对象
}

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    SuperType.call(this, name);
    this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
    alert(this.age);
};

这里写图片描述

这个例子的高效率体现在它只调用了一次 SuperType 构造函数,并且因此避免了在 SubType.
prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用
instanceof 和 isPrototypeOf()。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式

小结
ECMAScript 支持面向对象(OO)编程,但不使用类或者接口。对象可以在代码执行过程中创建和增强,因此具有动态性而非严格定义的实体。在没有类的情况下,可以采用下列模式创建对象。
工厂模式,使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。这个模式后来被构造函数模式所取代。

构造函数模式,可以创建自定义引用类型,可以像创建内置对象实例一样使用 new 操作符。不过,构造函数模式也有缺点,即它的每个成员都无法得到复用,包括函数。由于函数可以不局限于任何对象(即与对象具有松散耦合的特点),因此没有理由不在多个对象间共享函数。

原型模式,使用构造函数的 prototype 属性来指定那些应该共享的属性和方法。组合使用构造函数模式和原型模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法。

JavaScript 主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。这样,子类型就能够访问超类型的所有属性和方法,这一点与基于类的继承很相似。原型链的问题是对象实例共享所有继承的属性和方法,因此不适宜单独使用。解决这个问题的技术是借用构造函数,即在子类型构造函数的内部调用超类型构造函数。这样就可以做到每个实例都具有自己的属性,同时还能保证只使用构造函数模式来定义类型。使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。此外,还存在下列可供选择的继承模式。

原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造。

寄生式继承,与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。为了解决组合继承模式由于多次调用超类型构造函数而导致的低效率问题,可以将这个模式与组合继承一起使用。

寄生组合式继承,集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值