第十一章 面向对象原型继承

JavaScript有两种开发模式:1.函数化(过程化),2.面向对象(OOP)。面向对象的语言有一个标志,那就是类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。但是,ECMAScript没有类(class)的概念,因此,它的对象也与基于类的语言中的对象有所不同。

一、 对象

现在我想创建两个对象,分别是小强和小明,我们可以使用var obj = new Object这种方式,也可以使用以下方式。

const xiaoming = {
    name: 'xiaoming',
    age: 18,
    sayHello: function () {
        return `hello, my name is ${this.name}, I am ${this.age} years old.`;
    }
};
const xiaoqiang = {
    name: 'xiaoqiang',
    age: 16,
    sayHello: function () {
        return `hello, my name is ${this.name}, I am ${this.age} years old.`;
    }
};


console.log(xiaoming.sayHello());
// hello, my name is xiaoming, I am 18 years old.
console.log(xiaoqiang.sayHello());
// hello, my name is xiaoqiang, I am 16 years old.

这种方式创建出的两个对象,我们发现有很多相同之处,比如说他们都有name,age,都有一样的sayHello方法。可否精简一下写法呢?于是我在第二个对象处做了如下更改:

const xiaoqiang = xiaoming;
xiaoqiang.name = 'xiaoqiang';
xiaoqiang.age = 16;


console.log(xiaoming.sayHello());
// hello, my name is xiaoqiang, I am 16 years old.
console.log(xiaoqiang.sayHello());
// hello, my name is xiaoqiang, I am 16 years old.

这里xiaoqiang和xiaoming在内存中的指针其实是一样的,指向同一个地方。修改其中一个,另外一个也会被修改。这是一个错误的演示。

1. 工厂模式

为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法就是为了解决实例化对象产生大量重复的问题。其实就是写了一个函数,函数返回一个对象。这样就实现了传参而创造新的对象的想法。

// 工厂模式
function People(name, age) {
    var obj = {
        name,
        age,
        sayHello() {
            return `hello, my name is ${this.name}, I am ${this.age} years old.`;
        }
    };
    return obj;
}

const xiaoming = People('xiaoming', 18);
const xiaoqiang = People('xiaoqiang', 16);

console.log(xiaoming.sayHello());
// hello, my name is xiaoming, I am 18 years old.
console.log(xiaoqiang.sayHello());
// hello, my name is xiaoqiang, I am 16 years old.

工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法搞清楚他们是哪个对象的实例。instanceof运算符,验证原型对象与实例对象之间的关系

console.log(typeof xiaoming);  // Object
console.log(xiaoming instanceof Object);// true
console.log(xiaoming instanceof People);
// false 无法严重实例xiaoming与原型对象People之间的关系

ECMAScript中可以采用构造函数(构造方法)可用来创建特定的对象。类型于Object对象。

2. 构造函数

  • 构造函数没有new Object,创建对象的写法,但是后台会自动创建对象;
  • this指向了创建实例的空对象;
  • 构造函数不需要返回obj(对象引用)。后台自动返回;
function People(name, age) {
    this.name = name;
    this.age = age;
    this.sayHello = function () {
        return `hello, my name is ${this.name}, I am ${this.age} years old.`;
    };
}

const xiaoming = new People('xiaoming', 18);
const xiaoqiang = new People('xiaoqiang', 16);

console.log(xiaoming.sayHello());
// hello, my name is xiaoming, I am 18 years old.
console.log(xiaoqiang.sayHello());
// hello, my name is xiaoqiang, I am 16 years old.

使用构造函数方法,即解决了重复实例化的问题,又解决了对象识别问题。

console.log(typeof xiaoming);  // Object
console.log(xiaoming instanceof Object) // true
console.log(xiaoming instanceof People) 
// true 可以验证xiaoming这个实例 就是从原型对象People而来

3. 构造函数的一些规范

  • 构造函数也是函数,但是函数名第一个字母大写
  • 创建实例必须使用new运算符,如new Obj(),而且这里第一个字母也是大写
function People(name, age) {
    this.name = name;   // 实例属性
    this.age = age;     // 实例属性
    this.sayHello = function () {   // 实例方法
        return `hello, my name is ${this.name}, I am ${this.age} years old.`;
    };
}

const xiaoming = new People('xiaoming', 18);    // 创建实例
const xiaoqiang = new People('xiaoqiang', 16);  // 创建实例

构造函数和普通函数的唯一区别,就是他们的调用方式不一样。只不过,构造函数也是函数,必须用new运算符来调用,否则就是普通函数。

const xiaoming = new People('xiaoming', 18);   // 构造模式调用
console.log(xiaoming.sayHello());
// hello, my name is xiaoming, I am 18 years old.

People('xiaoqiang', 18);     // 普通模式调用无效

var xiaoqiang = {};
People.call(xiaoqiang, 'xiaoqiang', 16);
console.log(xiaoqiang.sayHello());
// hello, my name is xiaoqiang, I am 16 years old.
const people1 = new People('xiaoming', 18);
const people2 = new People('xiaoming', 18);

console.log(people1.name === people2.name);
// true 
console.log(people1.sayHello === people2.sayHello);
// false 实例后的方法是两个不同指针的引用类型对象 引用类型绝对不相等

引用地址不一致,这样造成了一些资源上的浪费。因为两者的属性方法是一致的。那么如何实现让他们的引用地址一致呢。第一个想到的是,把这个函数写在外面,如下:

function People(name, age, sayHello) {
    this.name = name;
    this.age = age;
    this.sayHello = sayHello;
}
function sayHello() {
    return `hello, my name is ${this.name}, I am ${this.age} years old.`;
}

const people1 = new People('xiaoming', 18);
const people2 = new People('xiaoming', 18);

console.log(people1.name === people2.name);
// true
console.log(people1.sayHello === people2.sayHello);
// true 通过全局实现引用地址的一致  但是不推荐这样写,会出现恶意调用,而且代码阅读起来不舒服
// 这样我们就用到了原型这个概念,也就是prototype

二、 原型

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。逻辑上可以这么理解:prototype是通过调用构造函数而创建的那个对象的原型对象。使用原型的好处可以让所有实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。

首先,我们需要牢记两点:①__proto__constructor属性是对象所独有的;② prototype属性是函数所独有的。但是由于JS中函数也是一种对象,所以函数也拥有__proto__constructor属性,这点是致使我们产生困惑的很大原因之一。

  • 每个对象都具有一个名为_proto_的属性;
  • 每个构造函数(构造函数标准为大写开头,如Function(),Object()等等JS中自带的构造函数,以及自己创建的)都具有一个名为prototype的方法(注意:既然是方法,那么就是一个对象(JS中函数同样是对象),所以prototype同样带有_proto_属性);
  • 每个对象的_proto_属性指向自身构造函数的prototype;

需要注意的指向是

Function的_proto_指向其构造函数Function的prototype;

Object作为一个构造函数(是一个函数对象!!函数对象!!),所以他的proto指向Function.prototype;

Function.prototype的_proto_指向其构造函数Object的prototype;

Object.prototype的_prototype_指向null(尽头);

下面这张图如果不要觉得麻烦,仔细阅读会被原型、原型链、构造器有些认识:

yuanxinglian.jpg
function People() {}
People.prototype.name = 'xiaoming';
People.prototype.age = 18;
People.prototype.sayHello = function sayHello() {
    return `hello, my name is ${this.name}, I am ${this.age} years old.`;
};
const xiaoming = new People();
const xiaoqiang = new People();

console.log(xiaoming.sayHello());
// hello, my name is xiaoming, I am 18 years old.
console.log(xiaoming.sayHello === xiaoqiang.sayHello);
// true
//如果是实例方法,不同的实例化,他们的方法地址是不一样的,是唯一的。
//如果是原型方法,那么他们的地址是共享的,大家都是一样的。

构造函数方式:

gouzao.png

构造函数方式创建的实例内部地址引用其实都不一样,只是不好测试,但是hello这个方法方便测试,因为是引用类型。

原型模式方式:

yuanxing.png

在原型模式声明中,多了两个属性,这两个属性都是创建对象时自动生成的。_proto_属性是实例指向原型对象People.prototype)的一个指针通过这两个属性,就可以访问到原型里的属性和方法了。

console.log(xiaoming.__proto__);
// {name: "xiaoming", age: 18, sayHello: ƒ, constructor: ƒ}
yuanxingjs.png
console.log(xiaoming.__proto__ === People.prototype);
// true 实例的proto属性就是指向构造函数的prototype

console.log(xiaoming.constructor);  
// ƒ People() {}
// 构造属性,可以获取函数本身
// 作用是被原型指针定位,然后得到构造函数本身
// 作用就是对象实例对应原型对象

console.log(People.constructor);  
// 构造函数的构造器指向Function

判断一个对象实例(对象引用)是不是指向了对象的原型对象,实例化自动指向。

console.log(People.prototype.isPrototypeOf(xiaoming)); //true

原型模式的执行流程:

  1. 先查找构造函数实例里的属性或方法,如果有,立刻返回;
  2. 如果构造函数实例没有,则取它的原型对象里查找,如果有,就返回;

虽然我们可以通过对象实例访问保存在原型中的值,但却不能访问通过对象实例重写原型中的值。

function People() {}
People.prototype.name = 'xiaoming';
People.prototype.age = 18;
People.prototype.sayHello = function () {
    return `hello, my name is ${this.name}, I am ${this.age} years old.`;
};
var xiaoming = new People;
console.log(xiaoming.name);
// xiaoming  原型中的值 实例没有 访问原型
xiaoming.name = 'xiaoqiang';
console.log(xiaoming.name);
// xiaoqiang     就近原则

删除实例中的属性

delete xiaoming.name;
console.log(xiaoming.name);
// xiaoming     已经被删除

删除和覆盖原型中的属性

delete xiaoming.name;
delete People.prototype.name;
console.log(xiaoming.name);
// undefined 原型属性被删除
People.prototype.name = 'kakaxi';
console.log(xiaoming.name);
// kakaxi  覆盖原型属性

如何判断属性是在构造函数的实例里,还是在原型里?可以使用hasOwnProperty()函数来验证:

console.log(xiaoming.hasOwnProperty('name'));
// 判断实例中是否有属性 有true 否则false
console.log('name' in xiaoming);
// 不管实例属性或原型属性,只要有该属性就返回true 两边都没有返回false

判断只有原型中有属性

function isProperty(object, property) {
    return !object.hasOwnProperty(property) && (property in object);
}
console.log(isProperty(xiaoming, 'name')); //true

为了让属性和方法更好的提现封装的效果,并且减少不必要的输入,原型的创建可以使用字面量的方式:

function People() {}
People.prototype = {
    name: 'xiaoming',
    age: 18,
    sayHello: function () {
        return `hello, my name is ${this.name}, I am ${this.age} years old.`;
    }
};

使用构造函数创建原型对象和使用字面量创建对象在使用上基本相同。但还有一些区别,字面量创建的方式使用constructor属性不会指向实例,而会指向Object,构造函数创建的方式则相反。

console.log(xiaoming.constructor);
//ƒ Object() { [native code] }

如果想让字面量的方式的constructor指向实例对象,那么可以这么做。

People.prototype = {
    constructor: People //强制修改指向
};

字面量方式为什么constructor会指向Object?因为People.prototype={}这种写法实际上是创建了一个对象。而每创建一个对象,就会同时创建它的prototype,这个对象也会自动获得constructor属性。所以,新对象的constructor重写了People原来的constructor,因此会指向新对象,那个新对象没有指定构造函数,那么默认为Object。

原型的声明是有先后顺序的,所以,重写原型会切断之前的原型。

People.prototype = {
    age: 20
};

var xiaoming = new People();
console.log(xiaoming.name); //undefined
//这里等于修改了对象指向,重写了对象,没有之前数据。

内置引用类型

console.log(Array.prototype.splice);
// ƒ splice() { [native code] }
console.log(String.prototype.split);
// ƒ split() { [native code] }

//内置引用类型拓展,但是不利于维护,一般不推荐使用
String.prototype.addString = function () {
    return this + ' ' + 'hello~~';
};

console.log('xiaoming'.addString());
// xiaoming hello~~

原型模式创建对象的缺点

它省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值是一样的。而原型最大的缺点就是他的优点,那就是共享。

原型中所有的属性是被很多实例共享的(不能传参)。共享对于函数非常合适,对于包含基本值得属性也还可以、但是对于属性包含引用类型,就存在一些问题。

function People() {}
People.prototype = {
    constructor: People,
    name: 'xiaoming',
    age: 18,
    friend: ['卡卡西', '鸣人', '佐助'],
    sayHello: function () {
        return `hello, my name is ${this.name}, I am ${this.age} years old.`;
    }
};

var xiaoming = new People();
xiaoming.friend.push('哈哈');    //在第一个实例修改后引用类型,保持了共享
console.log(xiaoming.friend);
["卡卡西", "鸣人", "佐助", "哈哈"]

var xiaoqiang = new People();
console.log(xiaoqiang.friend);   //共享xiaoming添加后的引用类型
["卡卡西", "鸣人", "佐助", "哈哈"]

数据共享的缘故,导致很多开发者放弃使用原型,因为每次实例化的数据需要保留自己的特性,而不能更改。

解决这种问题可以组合构造函数+原型模式:

function People(name, age) {
    this.name = name,
    this.age = age,
    this.friend = ['旗木卡卡西', '漩涡鸣人', '宇智波佐助']
}
People.prototype = {
    constructor: People,
    sayHello: function () {
        return `hello, my name is ${this.name}, I am ${this.age} years old.`;
    }
}

var xiaoming = new People('xiaoming', 18);
var libai = new People('libai', 20);
libai.friend.push('杜甫');
console.log(xiaoming.friend);
// ["旗木卡卡西", "漩涡鸣人", "宇智波佐助"]
console.log(libai.friend);
// ["旗木卡卡西", "漩涡鸣人", "宇智波佐助", "杜甫"]

这种混合模式很好解决了传参引用共享的问题,是创建对象比较好的办法。

原型模式,不管你是否调用了原型中的共享方法,它都会初始化原型中的方法,并且在声明一个对象时,构造函数+原型部分让人感觉又让人觉得很怪异。最好把构造函数和原型封装到一起。为了解决这个问题,我们可以使用动态原型模式。

动态原型模式

function People(name, age) {
    this.name = name;
    this.age = age;
    this.friend = ['旗木卡卡西', '漩涡鸣人', '宇智波佐助'];

    console.log('执行开始');
    People.prototype.sayHello = function () {
        return `hello, my name is ${this.name}, I am ${this.age} years old.`;
    }
    console.log('执行结束');
}
// 原型的初始化,只要第一次初始化就可以,没必要每次构造函数实例化的时候都初始化。
// 也就是new一个实例就初始化一次 以上console.log会分别执行实例个数次
var xiaoming = new People('xiaoming', 18);

var xiaoqiang = new People('xiaoqiang', 16);

解决方法:

    if (typeof this.hello !== 'function') {
        console.log('执行开始');
        People.prototype.sayHello = function () {
            return `hello, my name is ${this.name}, I am ${this.age} years old.`;
        };
        console.log('执行结束');
    }

使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会切换实例于新原型之间的联系。

以上讲解了各种方式创建对象,如果这几种方式不能满足需求,还可以使用一开始的那种方式:寄生构造函数。

三、继承

继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承。而ECMAScript只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成。

以下来自阮一峰博客,继承的五种方式。

简单说,现在有两个构造函数:

function Animal() {
    this.species = '动物';
}
function Cat(name, color) {
    this.name = name;
    this.color = color;
}

如何让猫继承动物呢

一、 构造函数绑定

第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:

function Cat(name, color) {
    Animal.apply(this, arguments);
    this.name = name;
    this.color = color;
}

const xiaobai = new Cat('xiaobai', 'white');
console.log(xiaobai.species); // 动物

二、 prototype模式

第二种方法更常见,使用prototype属性。

如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。

每个函数都有一个prototype对象,前面说过。prototype对象中默认含有constructor对象,这个对象指向创建这个实例的构造函数。

// 通过原型链继承,超类实例化后的对象实例,复制给子类型的原型属性
Cat.prototype = new Animal();
// Animal {species: "动物"}
console.log(xiaobai.species); // 动物

但是这样绑定后Cat.prototype.constructor指向了Animal,这显然会导致继承链的紊乱(cat1明明是用构造函数Cat生成的),因此我们必须手动纠正,将Cat.prototype对象的constructor值改为Cat。这就是第二行的意思。

Cat.prototype.constructor = Cat;
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

三、 直接继承prototype

第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。

现在,我们先将Animal对象改写:

function Animal(){ }
Animal.prototype.species = "动物";

然后,将Cat的prototype对象,然后指向Animal的prototype对象,这样就完成了继承。

Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。

所以,上面这一段代码其实是有问题的。请看第二行

Cat.prototype.constructor = Cat;

这一句实际上把Animal.prototype对象的constructor属性也改掉了!

alert(Animal.prototype.constructor); // Cat

四、 利用空对象作为中介

由于"直接继承prototype"存在上述的缺点,所以就有第四种方法,利用一个空对象作为中介。

var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;

F是空对象,所以几乎不占内存。这时,修改Cat的prototype对象,就不会影响到Animal的prototype对象。

alert(Animal.prototype.constructor); // Animal

我们将上面的方法,封装成一个函数,便于使用。

  function extend(Child, Parent) {
    var F = function(){};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
    Child.uber = Parent.prototype;
  }

使用的时候,方法如下

extend(Cat,Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

这个extend函数,就是YUI库如何实现继承的方法。

另外,说明一点,函数体最后一行

Child.uber = Parent.prototype;

意思是为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。(uber是一个德语词,意思是"向上"、"上一层"。)这等于在子对象上打开一条通道,可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。

五、 拷贝继承

上面是采用prototype对象,实现继承。我们也可以换一种思路,纯粹采用"拷贝"方法实现继承。简单说,如果把父对象的所有属性和方法,拷贝进子对象,不也能够实现继承吗?这样我们就有了第五种方法。

首先,还是把Animal的所有不变属性,都放到它的prototype对象上。

  function Animal(){}

  Animal.prototype.species = "动物";

然后,再写一个函数,实现属性拷贝的目的。

function extend2(Child, Parent) {
    var p = Parent.prototype;
    var c = Child.prototype;
    for (var i in p) {
        c[i] = p[i];
    }
    c.uber = p;
}

这个函数的作用,就是将父对象的prototype对象中的属性,一一拷贝给Child对象的prototype对象。

使用的时候,这样写:

extend2(Cat, Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物

非构造函数的继承

写到着 面向对象和原型继承有些晕。准备抽时间重新整理下。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容

  •   面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意...
    霜天晓阅读 2,107评论 0 6
  • 博客内容:什么是面向对象为什么要面向对象面向对象编程的特性和原则理解对象属性创建对象继承 什么是面向对象 面向对象...
    _Dot912阅读 1,421评论 3 12
  • 面向对象的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。ECMAScr...
    DHFE阅读 971评论 0 4
  • 当你驱车行使在大西北时,特别是在冬天,除了满目的荒凉,或许见的最多的就是戈壁荒滩里林立的风机,以及一眼看不到边境的...
    就爱嗑瓜子阅读 350评论 3 6
  • 从未想过禅舞会带给我如此大的震撼,这不仅仅是视觉的冲击,更是心灵的碰撞。当悲亢苍凉的《二泉映月》缓缓响起的...
    文武娃娃阅读 1,146评论 8 7