第 15 章 面向对象与原型

[复制链接]
发表于 2026-1-31 12:17:30 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
第 15 章 面向对象与原型

1.创建对象

2.原型

3.继续

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

创建一个对象,然后给这个对象新建属性和方法。
  1. var box = new Object();        //创建一个 Object 对象
  2. box.name = 'Lee';        //创建一个 name 属性并赋值
  3. box.age = 100;        //创建一个 age 属性并赋值
  4. box.run = function () {        //创建一个 run()方法并返回值
  5. return this.name + this.age + '运行中...';
  6. };
  7. alert(box.run());        //输出属性和方法的值
复制代码
上面创建了一个对象,而且创建属性和方法,在 run()方法里的 this,就是代表 box 对象自己。这种是 JavaScript 创建对象最根本的方法,但有个缺点,想创建一个类似的对象,就会产生大量的代码
  1. var box2 = box;        //得到 box 的引用
  2. box2.name = 'Jack';        //直接改变了 name 属性
  3. alert(box2.run());        //用 box.run()发现 name 也改变了
  4. var box2 = new Object(); box2.name = 'Jack'; box2.age = 200; box2.run = function () {
  5. return this.name + this.age + '运行中...';
  6. };
  7. alert(box2.run());        //这样才避免和 box 混淆,从而保持独立
复制代码
为了办理多个类似对象声明的题目,我们可以使用一种叫做工厂模式的方法,这种方法就是为了办理实例化对象产生大量重复的题目。
  1. function createObject(name, age) { //集中实例化的函数 var obj = new Object();
  2. obj.name = name; obj.age = age; obj.run = function () {
  3. return this.name + this.age + '运行中...';
  4. };
  5. return obj;
  6. }
  7. var box1 = createObject('Lee', 100);        //第一个实例
  8. var box2 = createObject('Jack', 200);        //第二个实例
  9. alert(box1.run());
  10. alert(box2.run());        //保持独立
复制代码
工厂模式办理了重复实例化的题目,但尚有一个题目,那就是辨认题目,由于根本无法搞清晰他们到底是哪个对象的实例。
  1. alert(typeof box1);        //Object
  2. alert(box1 instanceof Object);        //true
复制代码
ECMAScript 中可以采取构造函数(构造方法)可用来创建特定的对象。范例于 Object 对
象。
  1. function Box(name, age) {        //构造函数模式
  2. this.name = name;
  3. this.age = age;
  4. this.run = function () {
  5. return this.name + this.age + '运行中...';
  6. };
  7. }
  8. var box1 = new Box('Lee', 100);        //new Box()即可
  9. var box2 = new Box('Jack', 200);
  10. alert(box1.run());
  11. alert(box1 instanceof Box);        //很清晰的识别他从属于 Box
复制代码
使用构造函数的方法,即办理了重复实例化的题目,又办理了对象辨认的题目,但题目是,这里并没有 new Object(),为什么可以实例化 Box(),这个是那里来的呢?
使用了构造函数的方法,和使用工厂模式的方法他们差别之处如下:
1.构造函数方法没有体现的创建对象(newObject());
2.直接将属性和方法赋值给 this 对象;
3.没有 renturn 语句。
构造函数的方法有一些规范:
1.函数名和实例化构造名类似且大写,(PS:非欺压,但这么写有助于区分构造函数宁静凡函数);
2.通过构造函数创建对象,必须使用 new 运算符。
既然通过构造函数可以创建对象,那么这个对象是那里来的,new Object()在什么地方实行了?实行的过程如下:
1.当使用了构造函数,而且 new 构造函数(),那么就配景实行了 new Object();2.将构造函数的作用域给新对象,(即 new Object()创建出的对象),而函数体内的 this 就
代表 new Object()出来的对象。
3.实行构造函数内的代码;
4.返回新对象(配景直接返回)。
关于 this 的使用,this 实在就是代表当前作用域对象的引用。假如在全局范围 this 就代表 window 对象,假如在构造函数体内,就代表当前的构造函数所声明的对象。
  1. var box = 2;
  2. alert(this.box);        //全局,代表 window
复制代码
构造函数宁静凡函数的唯一区别,就是他们调用的方式差别。只不外,构造函数也是函数,必须用 new 运算符来调用,否则就是平常函数。
  1. var box = new Box('Lee', 100); //构造模式调用 alert(box.run());
  2. Box('Lee', 20);        //普通模式调用,无效
  3. var o = new Object();
  4. Box.call(o, 'Jack', 200) //对象冒充调用 alert(o.run());
复制代码
探究构造函数内部的方法(或函数)的题目,起首看下两个实例化后的属性或方法是否相
等。
  1. var box1 = new Box('Lee', 100);        //传递一致
  2. var box2 = new Box('Lee', 100);        //同上
  3. alert(box1.name == box2.name);        //true,属性的值相等
  4. alert(box1.run == box2.run);        //false,方法其实也是一种引用地址
  5. alert(box1.run() == box2.run());        //true,方法的值相等,因为传参一致
复制代码
可以把构造函数里的方法(或函数)用 new Function()方法来取代,得到一样的结果,更加
证实,他们终极判断的是引用地点,唯一性。
  1. function Box(name, age) {        //new Function()唯一性
  2. this.name = name;
  3. this.age = age;
  4. this.run = new Function("return this.name + this.age + '运行中...'");
  5. }
复制代码
我们可以通过构造函数表面绑定同一个函数的方法来包管引用地点的同等性,但这种做
法没什么须要,只是加深学习相识:
  1. function Box(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. this.run = run;
  5. }
  6. function run() {        //通过外面调用,保证引用地址一致
  7. return this.name + this.age + '运行中...';
  8. }
复制代码
固然使用了全局的函数 run()来办理了包管引用地点同等的题目,但这种方式又带来了一个新的题目,全局中的 this 在对象调用的时间是 Box 自己,而看成平常函数调用的时间, this 又代表 window。
二.原型

我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个对象,它的用途是包罗可以由特定范例的全部实例共享的属性和方法。逻辑上可以这么明白:prototype 通过调用构造函数而创建的谁人对象的原型对象。使用原型的利益可以让全部对象实例共享它所包罗的属性和方法。也就是说,不必在构造函数中界说对象信息,而是可以直接将这些信息添加到原型中。
  1. function Box() {}        //声明一个构造函数
  2. Box.prototype.name = 'Lee';        //在原型里添加属性
  3. Box.prototype.age = 100;
  4. Box.prototype.run = function () {        //在原型里添加方法
  5. return this.name + this.age + '运行中...';
  6. };
复制代码
比力一下原型内的方法地点是否同等:
  1. var box1 = new Box();
  2. var box2 = new Box();
  3. alert(box1.run == box2.run);        //true,方法的引用地址保持一致
复制代码
为了更进一步相识构造函数的声明方式和原型模式的声明方式,我们通过图示来相识一下:
构造函数方式
原型模式方式
在原型模式声明中,多了两个属性,这两个属性都是创建对象时主动天生的。__proto__属性是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性 constructor。通过这两个属性,就可以访问到原型里的属性和方法了。
PS:IE 欣赏器在脚本访问__proto__会不能辨认,火狐和谷歌欣赏器及其他某些欣赏器均能辨认。固然可以输出,但无法获取内部信息。
  1. alert(box1.__proto__);        //[object Object]
  2. 判断一个对象是否指向了该构造函数的原型对象,可以使用 isPrototypeOf()方法来测试。 alert(Box.prototype.isPrototypeOf(box)); //只要实例化对象,即都会指向
复制代码
原型模式的实行流程:
1.先查找构造函数实例里的属性或方法,假如有,立即返回;
2.假如构造函数实例里没有,则去它的原型对象里找,假如有,就返回;
固然我们可以通过对象实例访问生存在原型中的值,但却不能访问通过对象实例重写原型中的值。
  1. var box1 = new Box();
  2. alert(box1.name);        //Lee,原型里的值
  3. box1.name = 'Jack';
  4. alert(box.1name);        //Jack,就近原则,
  5. var box2 = new Box();
  6. alert(box2.name);        //Lee,原型里的值,没有被 box1 修改
复制代码
假如想要 box1 也能在反面继续访问到原型里的值,可以把构造函数里的属性删除即可,详细如下:
  1. delete box1.name;        //删除属性
  2. alert(box1.name);
复制代码
怎样判断属性是在构造函数的实例里,还是在原型里?可以使用 hasOwnProperty()函数来验证:
  1. alert(box.hasOwnProperty('name'));        //实例里有返回 true,否则返回 false
复制代码
构造函数实例属性和原型属性表示图
in 操纵符会在通过对象可以或许访问给定属性时返回 true,无论该属性存在于实例中还是原型中。
  1. alert('name' in box);        //true,存在实例中或原型中
复制代码
我们可以通过 hasOwnProperty()方法检测属性是否存在实例中,也可以通过 in 来判断实例或原型中是否存在属性。那么联合这两种方法,可以判断原型中是否存在属性。
  1. function isProperty(object, property) {        //判断原型中是否存在属性
  2. return !object.hasOwnProperty(property) && (property in object);
  3. }
  4. var box = new Box();
  5. alert(isProperty(box, 'name'))        //true,如果原型有
复制代码
为了让属性和方法更好的体现封装的结果,而且镌汰不须要的输入,原型的创建可以使用字面量的方式:
  1. function Box() {};
  2. Box.prototype = { //使用字面量的方式 name : 'Lee',
  3. age : 100,
  4. run : function () {
  5. return this.name + this.age + '运行中...';
  6. }
  7. };
复制代码
使用构造函数创建原型对象和使用字面量创建对象在使用上根本类似,但还是有一些区别,字面量创建的方式使用 constructor 属性不会指向实例,而会指向 Object,构造函数创建的方式则相反。
  1. var box = new Box();
  2. alert(box instanceof Box);
  3. alert(box instanceof Object);
  4. alert(box.constructor == Box);        //字面量方式,返回 false,否则,true
  5. alert(box.constructor == Object);        //字面量方式,返回 true,否则,false
  6. 如果想让字面量方式的 constructor 指向实例对象,那么可以这么做:
  7. Box.prototype = {
  8. constructor : Box,        //直接强制指向即可
  9. };
复制代码
PS:字面量方式为什么 constructor 会指向 Object?由于 Box.prototype={};这种写法实在
就是创建了一个新对象。而每创建一个函数,就会同时创建它 prototype,这个对象也会主动获取 constructor 属性。以是,新对象的 constructor 重写了 Box 原来的 constructor,因此会指向新对象,谁人新对象没有指定构造函数,那么就默以为 Object。
原型的声明是有先后次序的,以是,重写的原型会堵截之前的原型。
  1. function Box() {};
  2. Box.prototype = {        //原型被重写了
  3. constructor : Box,
  4. name : 'Lee',
  5. age : 100,
  6. run : function () {
  7. return this.name + this.age + '运行中...';
  8. }
  9. };
  10. Box.prototype = {
  11. age = 200
  12. };
  13. var box = new Box();        //在这里声明
  14. alert(box.run());        //box 只是最初声明的原型
复制代码
原型对象不但仅可以在自界说对象的环境下使用,而 ECMAScript 内置的引用范例都可以使用这种方式,而且内置的引用范例自己也使用了原型。
  1. alert(Array.prototype.sort);        //sort 就是 Array 类型的原型方法
  2. alert(String.prototype.substring);        //substring 就是 String 类型的原型方法
  3. String.prototype.addstring = function () {        //给 String 类型添加一个方法
  4. return this + ',被添加了!';        //this 代表调用的字符串
  5. };
  6. alert('Lee'.addstring());        //使用这个方法
复制代码
PS:只管给原生的内置引用范例添加方法使用起来特殊方便,但我们不保举使用这种方法。由于它大概会导致定名辩论,倒霉于代码维护。
原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是同等的。而原型最大的缺点就是它最大的优点,那就是共享。
原型中全部属性是被很多实例共享的,共享对于函数非常符合,对于包罗根本值的属性也还可以。但假如属性包罗引用范例,就存在肯定的题目:
  1. function Box() {}; Box.prototype = {
  2. constructor : Box,
  3. name : 'Lee',
  4. age : 100,
  5. family : ['父亲', '母亲', '妹妹'],        //添加了一个数组属性
  6. run : function () {
  7. return this.name + this.age + this.family;
  8. }
  9. };
  10. var box1 = new Box();
  11. box1.family.push('哥哥');        //在实例中添加'哥哥'
  12. alert(box1.run());
  13. var box2 = new Box();
  14. alert(box2.run());        //共享带来的麻烦,也有'哥哥'了
复制代码
PS:数据共享的缘故,导致很多开辟者放弃使用原型,由于每次实例化出的数据必要生存自己的特性,而不能共享。
为了办理构造传参和共享题目,可以组合构造函数+原型模式:
  1. function Box(name, age) {        //不共享的使用构造函数
  2. this.name = name;
  3. this.age = age;
  4. this. family = ['父亲', '母亲', '妹妹'];
  5. };
  6. Box.prototype = { //共享的使用原型模式 constructor : Box,
  7. run : function () {
  8. return this.name + this.age + this.family;
  9. }
  10. };
复制代码
PS:这种肴杂模式很好的办理了传参和引用共享的浩劫题。是创建对象比力好的方法。
原型模式,不管你是否调用了原型中的共享方法,它都会初始化原型中的方法,而且在声明一个对象时,构造函数+原型部门让人感觉又很怪异,最好就是把构造函数和原型封装到一起。为了办理这个题目,我们可以使用动态原型模式。
  1. function Box(name ,age) {        //将所有信息封装到函数体内
  2. this.name = name;
  3. this.age = age;
  4. if (typeof this.run != 'function') {        //仅在第一次调用的初始化
  5. Box.prototype.run = function () {
  6. return this.name + this.age + '运行中...';
  7. };
  8. }
  9. }
  10. var box = new Box('Lee', 100); alert(box.run());
复制代码
当第一次调用构造函数时,run()方法发现不存在,然后初始化原型。当第二次调用,就不会初始化,而且第二次创建新对象,原型也不会再初始化了。如许及得到了封装,又实现了原型方法共享,而且属性都保持独立。
  1. if (typeof this.run != 'function') { alert('第一次初始化'); //测试用
  2. Box.prototype.run = function () {
  3. return this.name + this.age + '运行中...';
  4. };
  5. }
  6. var box = new Box('Lee', 100);        //第一次创建对象
  7. alert(box.run());        //第一次调用
  8. alert(box.run());        //第二次调用
  9. var box2 = new Box('Jack', 200);        //第二次创建对象
  10. alert(box2.run());
  11. alert(box2.run());
复制代码
PS:使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,由于会堵截实例和新原型之间的接洽。
以上解说了各种方式对象创建的方法,假如这几种方式都不能满意需求,可以使用一开始那种模式:寄生构造函数。
  1. function Box(name, age) { var obj = new Object(); obj.name = name; obj.age = age;
  2. obj.run = function () {
  3. return this.name + this.age + '运行中...';
  4. };
  5. return obj;
  6. }
复制代码
寄生构造函数,实在就是工厂模式+构造函数模式。这种模式比力通用,但不能确定对象关系,以是,在可以使用之前所说的模式时,不发起使用此模式。
在什么环境下使用寄生构造函数比力符合呢?假设要创建一个具有额外方法的引用范例。由于之前分析不发起直接 String.prototype.addstring,可以通过寄生构造的方式添加。
  1. function myString(string) {
  2. var str = new String(string); str.addstring = function () {
  3. return this + ',被添加了!';
  4. };
  5. return str;
  6. }
  7. var box = new myString('Lee');        //比直接在引用原型添加要繁琐好多
  8. alert(box.addstring());
复制代码
在一些安全的环境中,比如克制使用 this 和 new,这里的 this 是构造函数里不使用 this,这里的 new 是在外部实例化构造函数时不使用 new。这种创建方式叫做稳妥构造函数。
  1. function Box(name , age) {
  2. var obj = new Object();
  3. obj.run = function () {
  4. return name + age + '运行中...';        //直接打印参数即可
  5. };
  6. return obj;
  7. }
  8. var box = Box('Lee', 100);        //直接调用函数
  9. alert(box.run());
复制代码
PS:稳妥构造函数和寄生类似。
四.继续
继续是面向对象中一个比力核心的概念。其他正统面向对象语言都会用两种方式实现继续:一个是接口实现,一个是继续。而 ECMAScript 只支持继续,不支持接口实现,而实现继续的方式依赖原型链完成。
  1. function Box() {        //Box 构造
  2. this.name = 'Lee';
  3. }
  4. function Desk() {        //Desk 构造
  5. this.age = 100;
  6. }
  7. Desk.prototype = new Box();        //Desc 继承了 Box,通过原型,形成链条
  8. var desk = new Desk();
  9. alert(desk.age);
  10. alert(desk.name);        //得到被继承的属性
  11. function Table() {        //Table 构造
  12. this.level = 'AAAAA';
  13. }
  14. Table.prototype = new Desk();        //继续原型链继承
  15. var table = new Table();
  16. alert(table.name);        //继承了 Box 和 Desk
复制代码
原型链继续流程图
假如要实例化 table,那么Desk实例中有age=100,原型中增长类似的属性 age=200,末了结果是多少呢?
  1. Desk.prototype.age = 200;        //实例和原型中均包含 age
  2. PS:以上原型链继承还缺少一环,那就是 Obejct,所有的构造函数都继承自 Obejct。而继承 Object 是自动完成的,并不需要程序员手动继承。
  3. 经过继承后的实例,他们的从属关系会怎样呢?
  4. alert(table instanceof Object);        //true
  5. alert(desk instanceof Table);        //false,desk 是 table 的超类
  6. alert(table instanceof Desk);        //true
  7. alert(table instanceof Box);        //true
复制代码
在 JavaScript 里,被继续的函数称为超范例(父类,基类也行,其他语言叫法),继续的函数称为子范例(子类,派生类)。继续也有之前题目,比如字面量重写原型会制止关系,使用引用范例的原型,而且子范例还无法给超范例通报参数。
为了办理引用共享和超范例无法传参的题目,我们采取一种叫借用构造函数的技能,大概成为对象假冒(伪造对象、经典继续)的技能来办理这两种题目。
  1. function Box(age) {
  2. this.name = ['Lee', 'Jack', 'Hello'] this.age = age;
  3. }
  4. function Desk(age) {
  5. Box.call(this, age);        //对象冒充,给超类型传参
  6. }
  7. var desk = new Desk(200); alert(desk.age); alert(desk.name);
  8. desk.name.push('AAA'); //添加的新数据,只给 desk alert(desk.name);
复制代码
借用构造函数固然办理了刚才两种题目,但没有原型,复用则无从谈起。以是,我们需
要原型链+借用构造函数的模式,这种模式成为组合继续。
  1. function Box(age) {
  2. this.name = ['Lee', 'Jack', 'Hello'] this.age = age;
  3. }
  4. Box.prototype.run = function () {
  5. return this.name + this.age;
  6. };
  7. function Desk(age) {
  8. Box.call(this, age);        //对象冒充
  9. }
  10. Desk.prototype = new Box();        //原型链继承
  11. var desk = new Desk(100);
  12. alert(desk.run());
复制代码
尚有一种继续模式叫做:原型式继续;这种继续借助原型并基于已有的对象创建新对象,同时还不必因此创建自界说范例。
  1. function obj(o) {        //传递一个字面量函数
  2. function F() {}        //创建一个构造函数
  3. F.prototype = o;        //把字面量函数赋值给构造函数的原型
  4. return new F();        //最终返回出实例化的构造函数
  5. }
  6. var box = {        //字面量对象
  7. name : 'Lee',
  8. arr : ['哥哥','妹妹','姐姐']
  9. };
  10. var box1 = obj(box);        //传递
  11. alert(box1.name);
  12. box1.name = 'Jack'; alert(box1.name);
  13. alert(box1.arr); box1.arr.push('父母'); alert(box1.arr);
  14. var box2 = obj(box);        //传递
  15. alert(box2.name);
  16. alert(box2.arr);        //引用类型共享了
复制代码
寄生式继续把原型式+工厂模式联合而来,目的是为了封装创建对象的过程。
  1. function create(o) {        //封装创建过程
  2. var f= obj(o);
  3. f.run = function () {
  4. return this.arr;        //同样,会共享引用
  5. };
  6. return f;
  7. }
复制代码
组合式继续是 JavaScript 最常用的继续模式;但,组合式继续也有一点小题目,就是超范例在使用过程中会被调用两次:一次是创建子范例的时间,另一次是在子范例构造函数的内部。
  1. function Box(name) {
  2. this.name = name;
  3. this.arr = ['哥哥','妹妹','父母'];
  4. }
  5. Box.prototype.run = function () { return this.name;
  6. };
  7. function Desk(name, age) {
  8. Box.call(this, name); //第二次调用 Box this.age = age;
  9. }
  10. Desk.prototype = new Box();        //第一次调用 Box
复制代码
以上代码是之前的组合继续,那么寄生组合继续,办理了两次调用的题目。
  1. function obj(o) {
  2. function F() {}
  3. F.prototype = o;
  4. return new F();
  5. }
  6. function create(box, desk) {
  7. var f = obj(box.prototype);
  8. f.constructor = desk;
  9. desk.prototype = f;
  10. }
  11. function Box(name) {
  12. this.name = name;
  13. this.arr = ['哥哥','妹妹','父母'];
  14. }
  15. Box.prototype.run = function () {
  16. return this.name;
  17. };
  18. function Desk(name, age) {
  19. Box.call(this, name);
  20. this.age = age;
  21. }
  22. inPrototype(Box, Desk);        //通过这里实现继承
  23. var desk = new Desk('Lee',100);
  24. desk.arr.push('姐姐');
  25. alert(desk.arr);
  26. alert(desk.run());        //只共享了方法
  27. var desk2 = new Desk('Jack', 200);
  28. alert(desk2.arr);        //引用问题解决
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表