首页 > 代码库 > 面向对象之笔记二——————原型与继承
面向对象之笔记二——————原型与继承
原型与继承
原型
为什么需要原型?
构造器创建对象的时候, 实际上会有成员重复 如果使用 构造器 this.方法名 = function .... 方式创建对象. 那么每一个对象对应的方法就会重复
function Person( name ) {
this.name = name;
this.sayHello = function() {
console.log( ‘你好,我是 ‘ + this.name );
};
}
var p1 = new Person( ‘Hello‘ );
var p2 = new Person( ‘JACK‘ );
p1.sayHello();
p2.sayHello();
// 只要执行一次函数, 那么函数内部的所有内容, 都会被创建出一次
// 由于函数 Person 创建对象的时候 会 同时生成 里面的 sayHello 方法
// 因此使得每一个 Person 的对象, 都包含一个内容完全相同, 不同的 sayHello 方法
console.log( p1.sayHello == p2.sayHello ); // false
// 由false可以看出每一个由构造函数 Person 创建的对象都会包含 一个 独立的 独有的 sayHello 方法,
// 所以内存消耗越来越大,会性能很差
解决的方法,让这个方法( 函数 )共享 ( 1 ). 将函数写到外面, 那么 Person 在初始化对象的时候就不会再创建一个函数了.只需要将 外面的函数引用 交给对象即可.缺点:一个对象可能有 n 多方法. 如果将所有的东西 都放到外面, 与其他库冲突的几率就会变大. 所以不宜采取该方法
var sayHello = function( ) {
console.log( ‘你好,我是 ‘ + this.name );
}
function Person( name ) {
this.name = name;
this.sayHello = sayHello;
}
var p1 = new Person( ‘Hello‘ );
var p2 = new Person( ‘JACK‘ );
p1.sayHello(); //你好,我是 Hello
p2.sayHello(); //你好,我是 JACK
console.log( p1.sayHello == p2.sayHello ); // true;
( 2 ). 将所有的方法( 函数 )都绑定到一个对象中
var tool = {
sayHello: function() {
console.log( ‘你好,我是 ‘ + this.name );
},
sleep: function() {},
eat: function() {}
}
function Person( name ) {
this.name = name;
this.sayHello = tool.sayHello;
}
var p1 = new Person( ‘Hello‘ ); //你好,我是 Hello
var p2 = new Person( ‘JACK‘ ); //你好,我是 JACK
p1.sayHello();
p2.sayHello();
console.log( p1.sayHello == p2.sayHello ); // true;
(3)js 原生就支持 解决该问题的办法,每一个函数都有 一个属性 prototype,该prototype属性指向一个对象. 每一个函数都存在该对象. (重点) 每一个由函数作为构造器创建的对象, 都会默认连接到该对象上.如果访问对象的方法, 而对象中没有定义, 就会在这个 构造函数.prototype表示的对象中去找. prototype 就是原型之意
function Foo() {
/* 只要函数存在 这个神秘的对象就存在了 */
/* Foo.prototype 就是该对象的引用 */
}
Foo.prototype.sayHello = function() {
console.log( ‘js 面向对象高级 ‘ );
};
var p = new Foo();
p.sayHello(); //js 面向对象高级
使用 prototype 改良代码,提高了效率,也不会对代码造成污染
function Person( name ) {
this.name = name;
}
Person.prototype.walk = function() {
console.log(this.name + ‘ walk‘)
};
Person.prototype.sayHello = function() {
console.log(this.name + ‘ say hello‘)
};
var p1 = new Person( ‘Jepson‘ );
var p2 = new Person( ‘Peter‘ );
p1.sayHello(); // Jepson say hello
p2.sayHello(); // Peter say hello
console.log( p1.sayHello === p2.sayHello ); // true;
原型属性 原型对象
针对 构造函数 而言, 原型就是 构造函数的 prototype 属性, 常常将其称为 原型属性. 针对 实例对象 而言, 原型就是 实例对象的 原型对象。
function Person () {} // 有了构造函数, 和 原型
var p = new Person(); // 有了实例
如何使用原型对象
简单的说就是将 共享的方法 放到原型中, 而 独有数据与行为 放在当前对象里。
- 通过对象动态的特性,直接给原型对象添加成员,Person.prototype 默认有 constructor 属性,指明对应构造函数 例: Person( name, age, gender, sayHello, eat, sleep )
// 首先需要构造函数, 原则就是将方法放在 原型中 function Person( name, age, gender ) { this.name = name; this.age = age; this.gender = gender; } /* 还需要添加方法 */ /* 原型也是对象 */ Person.prototype.sayHello = function() { console.log( ‘你好,我是‘ + this.name ); }; Person.prototype.eat = function() { console.log( this.name + "在吃饭" ); }; Person.prototype.sleep = function() { console.log( this.name + "在睡觉, 睡了 " + this.age + " 年了"); }; /* 直接给原型对象添加成员 */ var p1 = new Person( ‘lilei‘, 19, ‘男‘ ); var p2 = new Person( ‘meimei‘, 20, ‘女‘ ); p1.sayHello(); //你好,我是lilei p1.eat(); // lilei在吃饭 p1.sleep(); // lilei在睡觉, 睡了 19 年了 console.log(‘-----------------------------‘); p2.sayHello(); // 你好,我是meimei p2.eat(); // meimei在吃饭 p2.sleep(); // meimei在睡觉, 睡了 20 年了
- 直接替换原型对象, Person.prototype 的原有属性 constructor 被覆盖了,要手动添加 ( 注意: 手动的添加 constructor 属性, 表示对应的构造函数 )
function Person( name, age, gender ) { this.name = name; this.age = age; this.gender = gender; } /* 直接替换原型对象 */ Person.prototype = { constructor: Person,// 手动的添加 constructor 表示对应的构造函数 sayHello : function() { console.log( ‘你好,我是‘ + this.name ); }, eat: function() { console.log( this.name + "在吃饭" ); }, sleep: function() { console.log( this.name + "在睡觉, 睡了 " + this.age + " 年了"); } }; var p1 = new Person( ‘lilei‘, 19, ‘男‘ ); var p2 = new Person( ‘meimei‘, 20, ‘女‘ ); p1.sayHello(); //你好,我是lilei p1.eat(); // lilei在吃饭 p1.sleep(); // lilei在睡觉, 睡了 19 年了 console.log(‘-----------------------------‘); p2.sayHello(); // 你好,我是meimei p2.eat(); // meimei在吃饭 p2.sleep(); // meimei在睡觉, 睡了 20 年了
__proto__
-
和 prototype 是否存在关系? 早期浏览器是不支持 __proto__ 火狐率先使用该属性,然后用了呢,觉得非常好,基本现在的新浏览器都支持该属性, 但因为不标准,不兼容低版本浏览器,注意:IE8不支持。一般多用于测试 作用: 利用__proto__可以快速的访问原型
p1.__proto__ === Person.prototype; // true;
-
访问 使用构造函数, 就使用 prototype 属性访问原型 使用实例对象, 就使用 非标准的 proto 属性访问原型
使低版本浏览器支持__proto__
- 判断浏览器是否支持 proto
if ( {}.__proto__ ) { console.log( ‘支持‘ ); } else { console.log( ‘不支持‘ ); }
- 为了模拟该用法, 不使用 __proto__ 去访问原型 最保险的方式就是利用构造函数 通过 instance(实例).constructor 访问 instance(实例)对象属性,而instance对象属性中没有定义,会在Person.prototype中找,就找到了Person.prototype.constructor 有个问题:万一construcotr被覆盖掉了,就会获取失败,所以按照规范,我们替换掉原型对象后,要添加上 constructor 属性
function __getProto__( instance ) { return instance.constructor.prototype; } function Person() {} var p = new Person(); console.log( __getProto__( p ) ); console.log( __getProto__( p ) === p.__proto__ ); // true;
继承
什么是继承
- 什么是继承? 自己没有,别人有,拿过来自己用,就好像自己的一样。前面的原型基本上都是这样。
- 原型与实例对象: 在 js 中, 方法定义在原型对象中, 而属性定义在实例对象中。调用方法的时候, 实例对象本身是没有该成员的, 但是依旧可以调用该方法, 好像这个方法就是该实例对象的一样. 因此, 我们称该实例对象继承自 原型对象。
- 任何一个实例, 都是继承自其原型对象的. 即 原型式继承 可以理解为 1,绑定在构造函数的原型属性上的方法,将来通过构造函数创建的对象都具有这个方法。 2,想让某一类对象具有某个方法, 可以在该对象的构造函数的原型属性上绑定该方法
function Person( name, age, gender ) { this.name = name; } /* 直接替换原型对象 */ Person.prototype = { sayHello : function() { console.log( ‘你好,我是‘ + this.name ); }, }; var p1 = new Person( ‘lilei‘, 19, ‘男‘ ); p1.sayHello(); //你好,我是lilei
为什么需要继承
-
从编程的发展:复用( 重复使用 ) div 标签 是 标签对象,而 a 标签 也是标签对象 这两个标签本身不是同一个标签,但是,他们都有 nodeName, nodeType...等 所以我们就要思考,怎样用更少的代码实现更多的方法。 我们就可以提供一个 baseElement,让所有的标签都继承于这个 baseElement,那我们每个标签就都有这些属性方法了!所以我们需要继承。
-
从 js 的效率来看 共享特性 复用(组件化开发)
(了解)传统的编程语言的面向对象操作,例如 C++,JAVA等
-
对象: 是具有方法和属性的逻辑单元 在 js 中,函数是一个特殊的数据类型( 函数在js中是一等公民) js 对象就是键值对,值如果是数据,那么键值就是构成属性 如果值是函数,那么就构成方法
-
创建方式 类 -> 实例化 -> 对象( 实例 )
class Person { public string name; public int age; public string gender; pulbic void sayHello() { // .... } }
类,用来描述对象的结构,它就是一个模板
Person p = new Person(); // 利用模板创建对象 p.name; // 访问属性 p.sayHello(); //调用方法
传统的面向对象编程语言, 重点需要一个"模板", 即 类( class )
-
传统的继承 传统的继承是模板的继承。
// Student 继承自 Person 类 class Student : Person { // ... } Student s = new Student(); // 注意此时 s 就可以调用 sayHello 方法了 // 可以使用 name,age 和 gender 属性了
-
相关概念
/* 相关概念 类 class 模板 构造函数, 类名就是构造函数名 子类 subclass 派生的模板 原型设置为指定对象的构造函数 实例 instance 某个类的对象 ... 实例成员(实例方法, 实例属性) 静态成员 静态方法 ... 直接绑定在函数上的方法 静态属性 ... 直接绑定在函数上的属性 */ // js 代码 function Person () { this.name = ‘黄帝‘; this.age = 0; this.gender = ‘男‘; } // 不叫子类, 只是一个 Student 类 function Student() { } // 继承派生 成为子类 Student.prototype = new Person(); // 即完成派生 var s = new Student(); s.name // OK
属性访问原则(重点)
- 对象在调用方法或访问属性的时候, 首先在当前对象中查询. 如果有该成员使用并停止查找
- 如果没有该成员就在其原型对象中查找. 如果有该成员即使用, 并停止查找
- 如果还没有就到 该对象的 原型对象 的 原型对象中查找... ...
a.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__; // HTMLAnchorElement > HTMLElement > Element > ... > Object > null
-
最后会查到 Object.prototype 上. 如果还没有即 返回 undefined
function Person ( name ) { this.name = name; } Person.prototype.name = ‘小芳‘; Person.prototype.age = ‘18‘; var p = new Person( ‘李雷‘ ); console.log( p.name ); // 李雷 先找自己,找到了,不找了 console.log( p.age ); // 18 自己没有,找到原型中的 18,找到了,不找了 console.log( p.gender );// undefined 自己没有,原型也没有,往原型的原型找... 最后undefined
如果修改原型对象中的属性值会怎样
给当前对象的原型提供的属性赋值, 实际上是给当前对象动态添加添加了该属性的新成员 并不会修改运行对象中的成员.
function Person() { }
Person.prototype.name = ‘peter‘;
var p1 = new Person();
console.log( p1.name ); // perter 当前对象的原型提供的属性
p1.name = ‘lili‘;
console.log( p1.name ); // lili 添加一个 对象属性
console.log( p1.__proto__.name ); //peter继承属性不变
面向对象之笔记二——————原型与继承