JavaScript对象(概念,创建方式,继承)

来源:互联网 发布:js删除指定id的tr 编辑:程序博客网 时间:2024/05/19 17:22

对象

1 JavaScript对象是动态的—可以新增属性也可以删除属性,除了字符串、数字、true、false、null、undefined之外,JavaScript中的值都是对象。
用这些术语来对三类JavaScript对象和两类属性作区分:
1 内置对象(数组、函数、日期、正则表达式)
2 宿主对象(HTMLElement对象)
3 自定义对象(有运行中JavaScript代码创建的对象)
4 自有属性(直接在对象中定义的属性)
5 继承属性(在对象的原型对象中定义的属性)
2 对象直接量是一个表达式,这个表达式的每次运算都创建初始化一个新的的对象。每次计算对象直接量的时候,也都会计算他的每个属性的值。
3 所有通过对象直接量创建的对象都具有同一个原型对象,并可以通过JavaScript代码Object.prototype获得对原型对象的引用。通过关键字new和构造函数调用创建对象的原型就是构造函数的prototype属性的值。
4 没有原型的对象为数不多,Object.prototype就是其中之一。
5 Object.create()是一个静态函数,而不是提供给某个对象调用的方法。可以通过传入null来创建一个没有原型的新对象,此对象不会继承任何东西,甚至基础方法toString();可以通过传入Object.prototype来创建一个普通的对象。
6 方括号内的表达式必须返回字符串或者一个可以转换为字符串的值。
7 关联数组。又称为散列、映射或字典。
8 在JavaScript中只有在查询属性时才能体会到继承的存在,而设置则和继承无关,这是JavaScript的一个重要特性,该特性让程序员可以有选择地覆盖继承的属性。
9 属性不存在,访问不会报错;对象不存在,查询此对象上的属性访问则会报错。

10 在下列情景下给对象O设置属性P时会失败:

  • O中的P是只读的
  • O中的属性P时继承属性,且是只读的
  • O中不存在自有属性P

11 删除属性:

  • Delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性。
  • Delete运算符只能删除自有属性不能删除继承属性;
  • Delete不能删除那些可配置为false的属性
  • Delete不能删除不可配置的全局变量(var x = 1; delete this.x;)但是可以(this.x = 1;delete x;可以删除)

12 检测属性:

可以通过in运算符、hasOwnPreperty()和propertyIsEnumerable()方法来完成这个工作,甚至仅通过属性查询也可以做到这一点。
In可以检测出自有属性和继承属性
hasOwnProperty()检测给定的名字是否是对象的自有属性
propertyIsEnumerable()是hasOwnProperty()的增强版,检测到时自有属性且属性是可枚举性为true才会返回true。

13 枚举属性

  对象继承的内置方法不可枚举,但在代码中给对象添加的属性都是可枚举的

14 属性getter和setter

  由getter和setter定义的属性称做“存储器属性”,它不同于“数据属性”,存储器属性不具有可写性。

15 属性的特性:

  •   Object.getOwnPropertyDescriptor()只能得到自有属性的描述符。要获得继承属性的特性,需要遍历原型链用Object.getPrototypeOf();
    +   要设置属性的特性,或者让新建属性就要某种特性,则需要调用Object.definePeoperty(),传入要修改的对象、要创建或修改的属性的名称以及属性描述符对象。如果要同时修改或创建多个属性,则需要使用Object.defineProperties().
  •   使用extend()函数,这个函数把一个对象的属性复制到另一个对象中。只是简单复制属性名和值,并没有复制属性的特性,而且也没有复制存取器属性的getter和setter方法只是将它们简单地转换为静态的数据属性。

16 原型属性

要检测一个对象是否是另一个对象的原型,用isPrototypeOf()方法。

17 序列化对象

是指将对象的状态转换为字符串,也可将字符串还原为对象
JSON.stringify(o); //将对象序列化
JSON.parse(s); //深拷贝
JSON支持对象、数组、字符串、无穷大数字、true、false、null,并且它们可以序列化和还原。函数、RegExp、Error对象和undefined值不能序列化和还原。

18 对象方法

toString();
toLocalString();
toJSON();
valueOf();

6.1 理解对象

6.1.1 属性类型

  • 数据属性
    • Configurable:表示能否通过delete删除属性从而重新定义属性。
    • Enumerable:表示能否通过for-in循环返回属性。
    • Writable: 表示能否修改属性的值。
    • Value: 包含这个属性的数据值。

   要修改属性的默认的特性,必须使用Object.defineProperty()方法。例如:
Object.defineProperty(person,”name”,{ writable:false,value:”Nicholas”}
  可以多次调用Object.defineProperty()方法修改同一个属性,但在吧configurable特性设置为false之后就会有限制了。
在调用Object.defineProperty()方法时,如果不指定,configurable enumerable 和 writable特性的默认值都是false。
+ 访问器属性
+ Configurable:表示能否通过delete输出属性从而重新定义属性
+ Enumerable:表示能否通过for-in循环返回属性。
+ Get:在读取属性时调用的函数。
+ Set:在写入属性是调用的函数。

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

+ 定义多个属性
+ 使用Object.definePropertys()方法
+ 读取属性的特性
+ 使用Object.getOwnPropertyDescriptor()方法可以取得给定属性的描述符。

6.2 创建对象

6.2.1 工厂模式

这种模式抽象了创建具体对象的过程。工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。

    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("Nicholas",29,"Software Engineer");    var person2 = createPerson("Grag",27,"Doctor");

6.2.2构造函数模式

function Person(name,age,job){    this.name = name;    this.age = age;    this.job = job;    this.sayName = function(){        alert(this.name);    };}var person1 = new Person("Nicholas",29,"Software Engineer");var person2 = new Person('Garg",17,"Doctor");

与工厂模式中的createPerson()不同的是
+ 没有显式地创建对象
+ 直接将属性和方法赋值给this
+ 没有return语句

以这种方式调用构造函数实际上会经历以下各个步骤:
+ 创建一个新对象
+ 将构造函数的作用域赋值给新对象
+ 执行构造函数代码,为对象添加属性
+ 返回新对象

创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型;而这正是构造函数模式胜过工厂模式的地方。使用构造函数的主要问题就是每个方法都要在每个实例上重新创建一遍。
关于构造函数
将构造函数当做函数:任何函数,只要通过new 操作符来调用,那它就可以作为构造函数

//当作构造函数使用var person = new Person("Nicholas",29,"Software Engineer");person.sayName();//Nicholas//当作普通函数调用Person("Greg",27,"Doctor") ;// 添加到window全局作业域window.sayName();//"Greg"//在另一个对象的作用域中调用var o = new Object();Person.call(o,"Kristen",25,"Nurse");o.sayName();//Kristen,使用这种方法,调用后o就拥有了所有属性和sayName()方法了。

构造函数的问题
1) 每个方法都要在每个实例上重新创建一遍。
2) 例如上述的例子中person1 和 person2 中将包含一个都叫sayName() 的Function实例,因为函数也是对象。alert(person1.sayName == person2.sayName)//false即可证明。
3) 而创建两个完成同样任务的Function实例是没有必要的,故可以将构造函数改写为如下形式:

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("Nicholas",29,"Software Engineer");var person2 = new Person('Garg",17,"Doctor");    

将sayName设置成全局属性,使得所有实例共享全局作业域中定义的同一个sayName()函数。
然而同时引发了问题
+ 在全局作用域中定义的函数实际上只能被某个对象调用,使全局作用域名不副实。
+ 如果对象需要定义很多方法,就要定义很多全局函数,使得我们这个自定义的引用类型没有丝毫的封装性可言

6.2.3 原型模式

我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

function Person(){}Person.prototype.name = "Nichoals";Person.prototype.age = 29;Person.prototype.job = "Doctor";Person.sayName = function(){    alert(this.name);};var person1 = new Person();//形式和构造函数创建实例的形式一致。person1.sayName();var person2 = new Person();person2.sayName();alert(person1.sayName == person2.sayName);//true创建新的对象Person的方式与构造函数的形式一致都是采用new 关键字来创建,不同的是所有实例person1和person2共享Person对象的属性和方法。

1 理解原型属性

这里写图片描述
俩个方法:
isPrototypeOf
+ alert(Person.prototype.isPrototypeOf(person1));//true
+ alert(Person.prototype.isPrototypeOf(person2));//true
getPrototypeOf
+ alert(Object.getPrototypeOf(person1) == Person.prototype);//true
+ alert(Object.getPrototypeOf(person1).name);//”Nicholas”
  每当代码读取某个对象的某个属性时, 都会执行一次搜索,目标是具有给定名字的属性。搜索首先冲对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值。如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。这正是对象实例共享原型所保存的属性和方法的基本原理。实例中的同名属性会覆盖原型中的同名属性。
  使用hasOwnProperty()方法可以检测一个属性是存在于 实例 中,还是存在于原型中。

2 原型与in操作符

有两种方式使用in操作符:
+ 单独使用:in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。
+ 在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。
要取得对象上所有可枚举的实例属性,可以使用Object.keys()方法。这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。
如果你想得到所有实例属性,无论它是否可枚举,到可以使用
Object.getOwnPropertyNames()方法。

    var keys = Object.getOwnPropertyNames(Person.prototype);    alert(keys);//"constructor,name,age,job,sayName"

3 更简单的原型方法

function Person(){}Person.prototype = {    name:"Nicholas",    age:28,    job:"Software Engineer",    sayName:function(){        alert(this.name);    }};

6 原型对象的问题
  首先它忽略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。虽然这会在某种程度上带来一些不方便,但还不是原型的最大问题,原型模式的最大问题就是由其共享的本性所导致的。这种共享对于函数非常合适。对于那些包含基本值的属性倒也说得过去,然而对于包含引用类型值的属性来说,问题就比较突出了。

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

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

  使用构造函数模式定义实例属性,原型模式用于定义方法和共享的属性。结果就是,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。此外,这种混成模式还支持向构造函数传递参数;

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); //truealert(person1.sayName === person2.sayName); //false

6.2.5 动态原型模式

把所有信息都封装在构造函数中,而通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点。

function Person(name,age,job){    this.name = name;    this.age = age;    this.job = job;    //方法    if(typeof this.sayName != "function"){        Person.prototype.sayName = function(){            alert(this.name);        };    }}var friend = new Person("Nicholas"29,"Software Engineer");friend.sayName();

6.2.6 寄生构造函数模式

基本思想:创建一个函数,该函数的作用仅仅是封装创建对象的代码。然后再返回新创建的对象function Person(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 friend = new Person("Nicholas"29,"Software Engineer");friend.sayName();//"Nicholas"

除了使用new操作符并把使用的包装函数叫做构造函数外,其他的和工厂模式一样。通过在构造函数的末尾添加一个return 语句,可以重写调用构造函数时返回的值。
两点说明:
+ 返回的对象与构造函数或者与构造函数的原型属性没有关系,即构造函数返回的对象与在构造函数外部创建的对象没有什么不同
+ 不能依赖instanceof操作符来确定对象类型。

6.2.7 稳妥构造函数模式

稳妥对象:没有公共属性,而且其方法也不引用this的对象。
遵循与寄生构造函数类似的模式,但是有两点不同:
+ 新创建的对象不引用this;
+ 不使用new操作符调用构造函数。

function Person(name,age,job){    var o = new Object();    o.sayName = function(){        alert(name);    };    return o;}

6.3 继承

6.3.1 原型链

基本思想:利用原型上一个引用类型击沉给另一个引用类型的属性和方法。回顾:每个构造函数都有一个原型对象,每个原型对象都包含一个指向构造函数的指针,而实例包含一个指向原型对象的内部指针。function SuperType(){    this.property = true;}superType.prototype.getSuperValue = function(){    return this.property;};function subType(){    this.subproperty = false;}subType.prototype = new SuperType();//继承了SuperTypesubType.prototype.getSubValue = function(){    return this.subproperty;}var instance = new subType();alert(instance.getSuperValue());//true;继承是通过创建SuperType的实例,并将这个实例赋值给SubType.prototype实现的。

这里写图片描述
分析结果:instance指向SubType的原型,SubType的原型指向SuperType的原型,getSuperValue()仍然存在于SuperType.prototoype中,但是property则位于SubType.prototype中。这是因为property是实例属性,getSuperValue()是原型方法,而SubType.prototye是SuperType的实例,所以property位于该实例中了。

1 别忘记模式的原型 Object.prototype

这里写图片描述

2 确定原型与实例的关系

  • 通过instanceof 检测实例instance与 Object SuperType subType原型的构造函数关系, 都是true
    • alert(instance instanceof Object)
  • 使用isPrototypeOf() 返回的也都是true,只有是原型链中出现过的原型,都可以说是实例的原型
    • alert(Object.isPrototypeOf(instance));

3 谨慎地定义方法

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

 function SuperType(){    this.property = true;}superType.prototype.getSuperValue = function(){    return this.property;};function subType(){    this.subproperty = false;}subType.prototype = new SuperType();//继承了SuperTypesubType.prototype.getSubValue = function(){    return this.subproperty;}//重写超类型中的方法,将覆盖原来的方法subType.prototype.getSuperValue = function(){    return false;}var instance = new subType();alert(instance.getSuperValue());//false; 虽然subType的实例调用getSuperValue()时,会调用重写后的方法,但是SuperType的实例调用时还会使用原来的方法。

不能使用对象字面量创建原型方法

function SuperType(){    this.property = true;}superType.prototype.getSuperValue = function(){    return this.property;};function subType(){    this.subproperty = false;}subType.prototype = new SuperType();//继承了SuperTypesubType.prototype = {    getSubValue : function(){        return this.subproperty;    },    someOtherMethod:function(){        return false;    }  };    }var instance = new subType();alert(instance.getSuperValue());// error这是由于将原型替换成一个对象的字面量的问题,使得现在的原型是只包含Object的实例。

4 原型链的问题

  • 包含引用类型值的原型,会被所有实例共享。
  • 在创建子类型的实例时,不能向超类型的构造函数传递参数—-通过借用构造函数解决

6.3.2 借用构造函数

基本思想:在子类型构造函数的内部调用超类型构造函数,注意:**函数只是在特定环境中执行代码的对象,因此可以通过使用apply()和call()方法在(将来)新创建的对象上执行构造函数**function SuperType(){    this.colors = ["red","blue","green"];}funtion SubType(){    //继承了SuperType对象    SuperType.call(this);}var instance1 = new SubType();instance1.colors.push("black");alert(instance1.colors);//“red,blue,green,black"var instance2 = new SubType();alert(instance2.colors);//"red,blue,green"采用借用父级的构造函数,使得子类SubType实例中拥有colors属性的副本

1 传递参数

相对原型链方式,借用构造函数使得子类型可以向父类型构造函数传递参数

function SuperType(name){    this.name = name;}funtion SubType(){    //继承了SuperType对象,同时传递了参数    SuperType.call(this,"Nicholas");//this 为SuperType对象    //实例对象    this.age = 29;//为确保父类SuperType的构造函数不会重写子类型的属性,在调用之后,再添加子类型的属性}var instance = new SubType();alert(instance.name);//"Nicholas"alert(instance.age);//29

2 借用构造函数的问题

  • 无法避免构造函数自身的问题–方法都在构造函数中定义,因此函数复用无从谈起.
  • 在超类型的原型中定义的方法,对子类型而言是不可见的,结果所有类型都只能使用构造函数模式。

6.3.3 组合继承

思想:使用原型链实现对原型属性和方法的继承,而通过构造函数实现对实例属性的继承
优点:既能保证原型链属性和方法的复用,又能保证每个实例都有自己的属性。

function SuperType(name){    this.name = name;    this.colors = ["red","blue","green"];}SuperType.prototype.sayName = function(){    alert(this.name);};funtion SubType(name,age){    //继承了SuperType对象的属性    SuperType.call(this);    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();//Nicholasinstance1.sayAge();//29var instance2 = new SubType("Greg",27);alert(instance2.colors);//"red,blue,green"instance2.sayName();//Greginstance2.sayAge();//27

此处虽然colors是引用类型,但使用借用构造方法继承,所以不会共享。

6.3.4 原型式继承

基本思想:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型

function object(o){   function F(){};   F.prototype = o;   return new F();}var person ={   name:"Nicholas",   friends:["Shelby","Court","Van"]};var antherPerson = object(person);antherPerson.name="Greg";antherPerson.friends.push("Rob");var yetAntherPerson = object(person);//或者 var yeAntherPerson = Object.create(person);yetAntherPerson.name = "Linda";yetAntherPerson.friends.push("Barbie");alert(person.friends);//"shelby,Court,Van,Rob,Barbie"alert(antherPerson.friends);//"shelby,Court,Van,Rob,Barbie"alert(yetAntherPerson.friends);//"shelby,Court,Van,Rob,Barbie"//共享原型的引用类型值属性alert(person.name);//Nicholasalert(antherPerson.name);//Greg,当对象实例添加一个属性时,会覆盖同名原型属性alert(yetAntherPerson.name);//Linda

Object.create()方法规范化了原型继承,有俩个参数,传入一个参数的情况下和object()一样
+ 用做新对象的对象和(可选的)
+ 新对象定义额外属性的对象。

Object.create()的第二个参数与Object.defineProperties()方法的第二参数格式相同:每个属性都是通过自己的描述符定义的,指定的任何属性都会覆盖原型对象上的同名属性

var person ={   name:"Nicholas",   friends:["Shelby","Court","Van"]};var anotherPerson = Object.create(person,{    name:{        value:"Greg"    }});alert(anotherPerson.name);//Greg,重新定义friends也会覆盖

6.3.5 寄生式继承

基本思想:创建一个仅用于封装继承过程的函数,函数在内部以某种方式来增强对象,最后在返回对象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"新对象不仅具有person的所有属性和方法,还具有自己的sayHi方法

6.3.6 寄生组合式继承

组合继承是最常用的继承,但是其有个最大的问题就是无论在什么情况先,都会调用两次超类型构造函数

 function SuperType(name){    this.name = name;    this.colors = ["red","blue","green"];}SuperType.prototype.sayName = function(){    alert(this.name);};funtion SubType(name,age){    //继承了SuperType对象的属性    SuperType.call(this);//----第二次调用SuperType()    this.age = age;}//继承方法SubType.prototype = new SuperType();//-----第一次调用SuperType()SubType.prototype.constructor = SubType;SubType.prototype.sayAge = function(){    alert(this.age);}在第一次调用Supertype构造函数时,SubType.prototype 会得到name和colors俩个属性;它们都是SuperType的实例属性,只不过现在位于SubType.prototype中。当调用SubType构造函数时,第二次调用了SuperType的构造函数,这一次又在新对象上创建了实例属性name和colors。第二次创建的属性屏蔽了原型中的两个同名的属性。    

这里写图片描述

使用寄生组合式解决上述问题

基本模式: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);};funtion SubType(name,age){    SuperType.call(this);    this.age = age;}inheritPrototype(SubType,SuperType);SubType.prototype.sayAge = function(){    alert(this.age);}

6.4 总结

ECMAScript在没有类的情况下,使用下列模式创建对象
+ 工厂模式
+ 思想:使用简单的函数创建对象,可以为对象添加属性和方法,然后返回对象。
+ 优点:解决了创建多个相似对象的问题
+ 缺点:没有解决对象识别问题(即怎么知道一个对象的类型)
+ 构造函数模式
+ 思想:可以创建自定义的引用类型,可以向创建内置对象实例一样使用new 操作符
+ 优点:解决了对象识别问题
+ 缺点:每个成员都无法得到复用,包括函数。
+ 原型模式
+ 思想:使用构造函数的prototype属性来指定哪些应该共享的属性和方法。
+ 优点:解决了函数复用,使得所有对象实例共享对象的属性和方法
+ 缺点:有时候我们不是所有的属性都想要共享,对于基本类型属性我们可以通过在实例上添加同名属性对其覆盖,但是引用类型属性,问题就比较突出了
+ 组合构造函数和原型模式
+ 思想:用构造函数定义实例属性,用原型定义共享的属性和方法
+ 优点:解决了原型模式的共享问题

继承
+ JavaScript中主要使用原型链实现继承
+ 思想:原型链的实现是通过将一个类型的实例赋值给另一个对象的构造函数的原型实现的
+ 问题:对象实例共享所有继承的属性和方法,不适合单独使用。
+ 借用构造函数
+ 思想:在子类型构造函数的内部调用超类型的构造函数
+ 优点:解决对象实例共享所有继承的属性和方法,使得每个实例有自己的属性,同时还保证了只使用构造函数模式定义类型。
+ 问题:在超类型的原型中定义的方法,对子类型而言是不可见的,结果所有类型都只能使用构造函数模式
+ 组合继承
+ 思想:使用构造函数继承实例属性,通过原型链继承共享的属性和方法。
+ 优点:既继承了实例属性,有继承了共享的属性和方法
+ 缺点:超类的构造函数会被调用两次
+ 寄生组合式继承
+ 思想:不必为了指定子类型的原型而调用超类的构造函数
+ 优点:拥有组合继承的优点,同时解决了组合继承的两次调用超类型构造函数的问题
+ 缺点:复杂、难懂
+ 原型式继承
+ 思想:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型
+ 优点:不必创建自定义类型
+ 缺点:引用类型共享
+ 寄生式继承
+ 思想:创建一个仅用于封装继承过程的函数
+ 优点:可以继承超类的属性和方法,同时具有自己的方法
+ 缺点:引用类型共享问题??

原创粉丝点击