JavaScript面向对象详解
<div class="postTitle"><h1>JavaScript面向对象</h1>
<p class="md-end-block md-p"><span class="md-plain" style="font-size: 15px">面向对象是一种编程思想(oop)。很多个具有相同属性和行为的对象就可以抽象为类,对象是类的一个实例。JavaScript在ECMAScript 6中引入了类的概念。</span></p>
<p class="md-end-block md-p"><span class="md-plain" style="font-size: 15px">面向对象有三个基本特征:<strong>封装、继承、多态</strong></span></p>
<ul>
<li><span style="font-size: 15px"><strong>封装</strong>:就是将一类事物的<strong>属性和行为抽象成一个类</strong>,使其<strong>属性私有化</strong>,<strong>行为公开化</strong>,隐藏对象属性和实现细节,仅对外提供访问形式,提高安全性和代码复用性。</span></li>
<li><span style="font-size: 15px"><strong>继承</strong>:则是<strong>进一步将一类事物共有的属性和行为抽象成一个父类</strong>,而每个<strong>子类拥有父类的行为和属性,也有自己特有的行为和属性</strong>,扩展了已存在的代码块,进一步提高了代码的复用性。</span></li>
<li><span style="font-size: 15px"><strong>多态</strong>:是指允许不同类的对象对同一消息做出不同响应,从一定角度来 看,封装和继承几乎都是为多态而准备的,类中<strong>多个方法的重载叫多态</strong>,父子类中<strong>方法的覆盖也叫多态</strong>。提高了软件 的可重用性和可扩充性。</span></li>
</ul>
<h2>一. Javascript 面向对象编程:封装</h2>
</div>
<div id="cnblogs_post_body">
<p><span style="font-size: 15px">Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象。但是,它又不是一种真正的面向对象编程(OOP)语言,在es6以前因为它的语法中没有class(类)。</span></p>
<p><span style="font-size: 15px">那么,es6以前如果我们要把"属性"(property)和"方法"(method),封装成一个对象,甚至要从原型对象生成一个实例对象,我们应该怎么做呢?</span></p>
<h3>1. 生成对象的原始模式</h3>
<p>假定我们把猫看成一个对象,它有"名字"和"颜色"两个属性。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> Cat =<span style="color: rgba(0, 0, 0, 1)"> {
name : </span>''<span style="color: rgba(0, 0, 0, 1)">,
color : </span>''<span style="color: rgba(0, 0, 0, 1)">
}</span></pre>
</div>
<p>现在,我们需要根据这个原型对象,生成两个实例对象。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> cat1 = {}; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建一个空对象</span>
cat1.name = "大毛"; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 按照原型对象的属性赋值</span>
cat1.color = "黄色"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> cat2 =<span style="color: rgba(0, 0, 0, 1)"> {};
cat2.name </span>= "二毛"<span style="color: rgba(0, 0, 0, 1)">;
cat2.color </span>= "黑色";</pre>
</div>
<p>好了,这就是最简单的封装了。但是,这样的写法有两个缺点,一是如果多生成几个实例,写起来就非常麻烦;二是实例与原型之间,没有任何办法,可以看出有什么联系。</p>
<h3>2. <span class="md-plain md-expand">使用工厂模式来构建对象</span></h3>
<p>我们可以写一个工厂函数,<span class="md-plain md-expand">手动初始化一个对象</span><span class="md-plain">,给这个对象添加属性和方法</span><span class="md-plain md-expand">,将这个对象返回,</span>解决代码重复的问题。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> factory(age){
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">声明一个对象</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> obj = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Object()
obj.age </span>=<span style="color: rgba(0, 0, 0, 1)"> age
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> obj
}</span></pre>
</div>
<p>然后生成实例对象,就等于是在调用函数:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> person1 = factroy(18<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">var</span> person2 = factroy(20)</pre>
</div>
<p>这种方法的问题依然是,person1和person2之间没有内在的联系,<span class="md-plain md-expand">不能很好的去将细节补充,</span><span class="md-plain md-expand">instanceof 返回的值只能是Object,</span>不能反映出它们是同一个原型对象的实例。</p>
<h3>3. 构造函数模式</h3>
<p>为了解决从原型对象生成实例的问题,Javascript提供了一个构造函数(Constructor)模式。</p>
<p>所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。</p>
<p>比如,人的原型对象现在可以这样写</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Person(age, name){
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.age =<span style="color: rgba(0, 0, 0, 1)"> age
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.name =<span style="color: rgba(0, 0, 0, 1)"> name
}</span></pre>
</div>
<p>我们现在就可以生成实例对象了。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">new关键词来构建</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> person1 = <span style="color: rgba(0, 0, 255, 1)">new</span> Person(18,'zhangsan'<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">var</span> person2 = <span style="color: rgba(0, 0, 255, 1)">new</span> Person(20,'lisi'<span style="color: rgba(0, 0, 0, 1)">)
console.log(person1.name) //zhangsan
console.log(person2.name) //lisi</span></pre>
</div>
<p>这时person1和person2会自动含有一个constructor属性,指向它们的构造函数。</p>
<div class="cnblogs_code">
<pre>console.log(person1.constructor == Person); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">true</span>
console.log(person2.constructor == Person); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">true</span></pre>
</div>
<p>Javascript还提供了一个<strong>instanceof运算符,验证原型对象与实例对象之间的关系</strong>。</p>
<div class="cnblogs_code">
<pre>console.log(person1 <span style="color: rgba(0, 0, 255, 1)">instanceof</span> Person); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">true</span>
console.log(person2 <span style="color: rgba(0, 0, 255, 1)">instanceof</span> Person); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">true</span></pre>
</div>
<h3>4. 构造函数模式的问题</h3>
<p>构造函数方法很好用,但是存在一个浪费内存的问题。</p>
<p>我们现在为Cat对象添加一个不变的属性"type"(种类),再添加一个方法eat(吃老鼠)。那么,原型对象Cat就变成了下面这样:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Cat(name, color) {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.name =<span style="color: rgba(0, 0, 0, 1)"> name;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.color =<span style="color: rgba(0, 0, 0, 1)"> color;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.type = "猫科动物"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.eat = <span style="color: rgba(0, 0, 255, 1)">function</span> () { alert("吃老鼠"<span style="color: rgba(0, 0, 0, 1)">); };
}</span></pre>
</div>
<p>还是采用同样的方法,生成实例:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> cat1 = <span style="color: rgba(0, 0, 255, 1)">new</span> Cat("大毛","黄色"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> cat2 = <span style="color: rgba(0, 0, 255, 1)">new</span> Cat ("二毛","黑色"<span style="color: rgba(0, 0, 0, 1)">);
alert(cat1.type); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 猫科动物</span>
cat1.eat(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 吃老鼠</span></pre>
</div>
<p>表面上好像没什么问题,但是实际上这样做,有一个很大的弊端。那就是对于每一个实例对象,type属性和eat()方法都是一模一样的内容,每一次生成一个实例,都必须为重复的内容,多占用一些内存,缺乏效率。</p>
<div class="cnblogs_code">
<pre>alert(cat1.eat == cat2.eat); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">false</span></pre>
</div>
<p>能不能让type属性和eat()方法在内存中只生成一次,然后所有实例都指向那个内存地址呢?</p>
<p>回答:是可以的。</p>
<h3>5. Prototype模式</h3>
<p>JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。</p>
<p>这意味着,我们可以把那些不变的属性和方法,直接定义在prototype对象上。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Cat(name, color) {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.name =<span style="color: rgba(0, 0, 0, 1)"> name;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.color =<span style="color: rgba(0, 0, 0, 1)"> color;
}
Cat.prototype.type </span>= "猫科动物"<span style="color: rgba(0, 0, 0, 1)">;
Cat.prototype.eat </span>= <span style="color: rgba(0, 0, 255, 1)">function</span> () { alert("吃老鼠") };</pre>
</div>
<p>然后,生成实例。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> cat1 = <span style="color: rgba(0, 0, 255, 1)">new</span> Cat("大毛","黄色"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> cat2 = <span style="color: rgba(0, 0, 255, 1)">new</span> Cat("二毛","黑色"<span style="color: rgba(0, 0, 0, 1)">);
console.log(cat1.type); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 猫科动物</span>
cat1.eat(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 吃老鼠</span></pre>
</div>
<p>这时所有实例的type属性和eat()方法,其实都是同一个内存地址,指向prototype对象,因此就提高了运行效率。</p>
<div class="cnblogs_code">
<pre>console.log(cat1.name == cat2.name); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">false</span>
console.log(cat1.eat == cat2.eat); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">true</span>
console.log(cat1.type == cat2.type); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">true</span></pre>
</div>
<h3>6. Prototype模式的验证方法</h3>
<h4>6.1 isPrototypeOf()</h4>
<p>这个方法用来判断,某个proptotype对象和某个实例之间的关系。</p>
<div class="cnblogs_code">
<pre>console.log(Cat.prototype.isPrototypeOf(cat1)); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">true</span>
console.log(Cat.prototype.isPrototypeOf(cat2)); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">true</span></pre>
</div>
<h4>6.2 hasOwnProperty()</h4>
<p>每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。</p>
<div class="cnblogs_code">
<pre>console.log(cat1.hasOwnProperty("name")); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> true</span>
console.log(cat1.hasOwnProperty("type")); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> false</span></pre>
</div>
<h4>6.3 in运算符</h4>
<p>in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。</p>
<div class="cnblogs_code">
<pre>console.log("name" <span style="color: rgba(0, 0, 255, 1)">in</span> cat1); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> true</span>
console.log("type" <span style="color: rgba(0, 0, 255, 1)">in</span> cat1); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> true</span></pre>
</div>
<p>in运算符还可以用来遍历某个对象的所有属性。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> prop <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> cat1) {
console.log(`cat1[${prop}]</span>=<span style="color: rgba(0, 0, 0, 1)">${cat1}`);
}</span></pre>
</div>
<p>结果为:</p>
<p><img src="https://img2022.cnblogs.com/blog/2938499/202208/2938499-20220818205252957-1788697320.png"></p>
<h3>7.<span class="md-plain">利用类里面的构造器来构建对象,es6的新特性</span></h3>
<p><span class="md-plain">创建一个基于原型继承的具有给定名称的新类,跟上述</span>Prototype模式类似。将私有化的属性和方法放置在 constructor 构造函数中,会随着实例对象的创建重新声明到实例对象身上。可以把那些共有不变的属性和方法,放置在外面,它会被定义在类的 prototype 对象上。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class Person {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">constructor 是一个构造函数,其里面的属性和方法会随着实例对象的创建重新声明到实例对象身上</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">constructor 里面的属性和方法不存在于在 Person类 的原型 prototype 身上</span>
constructor(name = 'jack', age = 18<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这里的this指向Person的实例对象</span>
<span style="color: rgba(0, 0, 255, 1)">this</span>.name =<span style="color: rgba(0, 0, 0, 1)"> name;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.age =<span style="color: rgba(0, 0, 0, 1)"> age;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.run = <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () {
console.log(</span>'跑'<span style="color: rgba(0, 0, 0, 1)">);
};
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">里面的方法存在于在 Person类 的原型 prototype 身上 只声明一次</span>
<span style="color: rgba(0, 0, 0, 1)"> sayHi() {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这里的this是指向它的调用者</span>
console.log(<span style="color: rgba(0, 0, 255, 1)">this</span>.name + ' hi~'<span style="color: rgba(0, 0, 0, 1)">);
console.log(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
}
}</span></pre>
</div>
<p>每次通过new关键词创建一个实例对象都会开辟一个内存空间存放新的对象</p>
<div class="cnblogs_code">
<pre>let person1 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Person();
let person2 </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Person();
console.log(person1); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Person类型的对象 {name: 'jack', age: 18, run: ƒ}</span>
console.log(person2); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Person类型的对象 {name: 'jack', age: 18, run: ƒ}</span>
console.log(person1 === person2); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">false</span>
console.log(<span style="color: rgba(0, 0, 255, 1)">new</span> Person() === <span style="color: rgba(0, 0, 255, 1)">new</span> Person()); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">false</span></pre>
</div>
<div>实例对象person1(person2)的 __proto__ 指向构造它的类的原型对象 Person.prototype,constructor里面的属性和方法不存在于在 Person类 的原型 prototype 身上</div>
<div class="cnblogs_code">
<pre>console.log(person1.__proto__); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">指向 Person.prototype</span>
console.log(person2.__proto__); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">指向 Person.prototype</span>
<span style="color: rgba(0, 0, 0, 1)">console.log(Person.prototype);
console.log(Person.prototype.run); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">undefined constructor里面的属性和方法不存在于在 Person类 的原型 prototype 身上</span>
console.log(person1.__proto__ === person2.__proto__); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">true</span>
console.log(person1.__proto__ === Person.prototype); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">true</span></pre>
</div>
<h2><img src="https://img2022.cnblogs.com/blog/2938499/202208/2938499-20220818212304757-1446960926.png"></h2>
<div>
<div>constructor构造函数里面的属性和方法会随着实例对象的创建重新声明到实例对象身上,constructor函数外的方法不会重新声明,始终存在于类的原型对象 prototype 身上</div>
<div>
<div class="cnblogs_code">
<pre>console.log(person1.run === person2.run); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">false</span>
console.log(person1.sayHi === person2.sayHi); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">true</span>
console.log(person1.sayHi === Person.prototype.sayHi); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">true</span></pre>
</div>
<div>实例对象通过其 __proto__ 属性可访问到类的原型 prototype 上的方法</div>
<div class="cnblogs_code">
<pre>person1.sayHi(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这个函数里的this指向实例对象person1打印:jack hi~</span>
person1.__proto__.sayHi(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这个函数里的this指向Person.prototype打印:undefined hi~</span>
console.log(person1.sayHi === person1.__proto__.sayHi); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">true</span></pre>
</div>
<p><img src="https://img2022.cnblogs.com/blog/2938499/202208/2938499-20220818213020584-541282998.png"></p>
<p>7.static 静态修饰</p>
<p class="md-end-block md-p"><span class="md-plain">static修饰的函数 使用 类名.方法名 调用</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class Person{
constructor(){
}
run(){
console.log(</span>'run'<span style="color: rgba(0, 0, 0, 1)">);
}
static sayHello(){
console.log(</span>'hello'<span style="color: rgba(0, 0, 0, 1)">);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">new</span> Person().run() <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">run</span>
Person.sayHello() <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">hello</span></pre>
</div>
<p class="md-end-block md-p"><span class="md-plain">如果不写static修饰,不是静态方法,它存在于类的constructor上面,不可以直接通过类去访问,而是要通过实例对象调用,或者通过Person.prototype.run()调用</span></p>
<p class="md-end-block md-p md-focus"><img src="https://img2022.cnblogs.com/blog/2938499/202208/2938499-20220820172656810-1819700786.png"></p>
</div>
</div>
<h2>二. Javascript 面向对象编程:构造函数的继承</h2>
<p>本节主要介绍,如何生成一个"继承"多个对象的实例。</p>
<p>比如,现在有一个"动物"对象的构造函数</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Animal() {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.species = "动物"<span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<p>还有一个"猫"对象的构造函数,</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Cat(name, color) {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.name =<span style="color: rgba(0, 0, 0, 1)"> name;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.color =<span style="color: rgba(0, 0, 0, 1)"> color;
}</span></pre>
</div>
<p>怎样才能使"猫"继承"动物"呢?</p>
<h3 class="md-end-block md-heading md-focus"><span class="md-plain md-expand">1.对象冒充</span></h3>
<p class="md-end-block md-heading md-focus"><span class="md-plain md-expand">直接改this指向,但是这种方法,找不到原型上的内容,无法继承原型</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Cat() {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.name =<span style="color: rgba(0, 0, 0, 1)"> name;
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.color =<span style="color: rgba(0, 0, 0, 1)"> color;
Animal.call(</span><span style="color: rgba(0, 0, 255, 1)">this</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">将Animal里面的this改成Cat里面的this </span>
<span style="color: rgba(0, 0, 0, 1)">}
let cat </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Cat();
console.log(cat.species);</span></pre>
</div>
<h3>2. <span class="md-plain md-expand">原型继承</span></h3>
<p>更常见的做法,则是使用prototype属性。</p>
<p>如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。</p>
<div class="cnblogs_code">
<pre>Cat.prototype = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Animal();
Cat.prototype.constructor </span>=<span style="color: rgba(0, 0, 0, 1)"> Cat;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> cat1 = <span style="color: rgba(0, 0, 255, 1)">new</span> Cat("大毛","黄色"<span style="color: rgba(0, 0, 0, 1)">);
alert(cat1.species); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 动物</span></pre>
</div>
<p>代码的第一行,我们将Cat的prototype对象指向一个Animal的实例。</p>
<div class="cnblogs_code">
<pre data-index="22">Cat.prototype = new Animal();</pre>
</div>
<p>它相当于完全删除了prototype 对象原先的值,然后赋予一个新值。但是,第二行又是什么意思呢?</p>
<div class="cnblogs_code">
<pre data-index="23">Cat.prototype.constructor = Cat;</pre>
</div>
<p>原来,任何一个prototype对象都有一个constructor属性,指向它的构造函数。也就是说,Cat.prototype 这个对象的constructor属性,是指向Cat的。</p>
<p>我们在<strong>前一步已经删除了这个prototype对象原来的值,所以新的prototype对象没有constructor属性,所以我们必须手动加上去,否则后面的"继承链"会出问题。</strong>这就是第二行的意思。</p>
<p>总之,这是很重要的一点,编程时务必要遵守。下文都遵循这一点,即如果替换了prototype对象,</p>
<div class="cnblogs_code">
<pre>o.prototype = {};</pre>
</div>
<p>那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。</p>
<div class="cnblogs_code">
<pre data-index="25">o.prototype.constructor = o;</pre>
</div>
<h3>3. 直接继承prototype</h3>
<p>由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。</p>
<p>现在,我们先将Animal对象改写:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Animal(){ }
Animal.prototype.species </span>= "动物";</pre>
</div>
<p>然后,将Cat的prototype对象,然后指向Animal的prototype对象,这样就完成了继承。</p>
<div class="cnblogs_code">
<pre>Cat.prototype =<span style="color: rgba(0, 0, 0, 1)"> Animal.prototype;
CatCat.prototype.constructor </span>=<span style="color: rgba(0, 0, 0, 1)"> Cat;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> cat1 = <span style="color: rgba(0, 0, 255, 1)">new</span> Cat("大毛","黄色"<span style="color: rgba(0, 0, 0, 1)">);
alert(cat1.species); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 动物</span></pre>
</div>
<p>与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是<strong> Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。</strong></p>
<p>所以,上面这一段代码其实是有问题的。请看第二行</p>
<div class="cnblogs_code">
<pre data-index="28">Cat.prototype.constructor = Cat;</pre>
</div>
<p>这一句实际上把Animal.prototype对象的constructor属性也改掉了!</p>
<div class="cnblogs_code">
<pre data-index="29">alert(Animal.prototype.constructor); // Cat</pre>
</div>
<h3>4. 利用空对象作为中介</h3>
<p>由于"直接继承prototype"存在上述的缺点,所以可以利用一个空对象作为中介。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> F = <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(){};
F.prototype </span>=<span style="color: rgba(0, 0, 0, 1)"> Animal.prototype;
Cat.prototype </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> F();
Cat.prototype.constructor </span>= Cat;</pre>
</div>
<p>F是空对象,所以几乎不占内存。这时,修改Cat的prototype对象,就不会影响到Animal的prototype对象。</p>
<div class="cnblogs_code">
<pre>alert(Animal.prototype.constructor); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Animal</span></pre>
</div>
<h3>5. prototype模式的封装函数</h3>
<p>我们将上面的方法,封装成一个函数,便于使用。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> extend(Child, Parent) {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> F = <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () { };
F.prototype </span>=<span style="color: rgba(0, 0, 0, 1)"> Parent.prototype;
Child.prototype </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> F();
Child.prototype.constructor </span>=<span style="color: rgba(0, 0, 0, 1)"> Child;
Child.uber </span>=<span style="color: rgba(0, 0, 0, 1)"> Parent.prototype;
}</span></pre>
</div>
<p>使用的时候,方法如下</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">extend(Cat, Animal);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> cat1 = <span style="color: rgba(0, 0, 255, 1)">new</span> Cat("大毛", "黄色"<span style="color: rgba(0, 0, 0, 1)">);
alert(cat1.species); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 动物</span></pre>
</div>
<p>这个extend函数,就是<strong>YUI库如何实现继承</strong>的方法。</p>
<p>另外,说明一点。函数体最后一行</p>
<div class="cnblogs_code">
<pre>Child.uber = Parent.prototype;</pre>
</div>
<p>意思是为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。这等于是在子对象上打开一条通道,可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。</p>
<h3>6. 拷贝继承</h3>
<p>上面是采用prototype对象,实现继承。我们也可以换一种思路,纯粹采用"拷贝"方法实现继承。简单说,如果把父对象的所有属性和方法,拷贝进子对象,不也能够实现继承吗?</p>
<p>首先,还是把Animal的所有不变属性,都放到它的prototype对象上。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Animal(){}
Animal.prototype.species </span>= "动物";</pre>
</div>
<p>然后,再写一个函数,实现属性拷贝的目的。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> extend2(Child, Parent) {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> p =<span style="color: rgba(0, 0, 0, 1)"> Parent.prototype;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> c =<span style="color: rgba(0, 0, 0, 1)"> Child.prototype;
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> i <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> p) {
c </span>=<span style="color: rgba(0, 0, 0, 1)"> p;
}
c.uber </span>=<span style="color: rgba(0, 0, 0, 1)"> p;
}</span></pre>
</div>
<p>这个函数的作用,就是将父对象的prototype对象中的属性,一一拷贝给Child对象的prototype对象。</p>
<p>使用的时候,这样写:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">extend2(Cat, Animal);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> cat1 = <span style="color: rgba(0, 0, 255, 1)">new</span> Cat("大毛","黄色"<span style="color: rgba(0, 0, 0, 1)">);
alert(cat1.species); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 动物</span></pre>
</div>
<h3>7.<span class="md-plain md-expand">es6中 </span><span class="md-plain md-expand">extends 关键词实现继承</span></h3>
<p><span class="md-plain md-expand">类似于上面</span>prototype模式的封装函数,继承的子类中的constructor如果要用this则一定要写super()</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class Person{
constructor(){
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.username = 'jack'<span style="color: rgba(0, 0, 0, 1)">
}
}
class Son extends Person{
constructor(){
super()
}
}
console.log(</span><span style="color: rgba(0, 0, 255, 1)">new</span> Son().username) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">jack</span></pre>
</div>
<h3 class="md-end-block md-heading md-focus"><span class="md-plain md-expand">8.组合继承 (原型+call改this指向)</span></h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">组合继承</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Person() {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.password = '123'<span style="color: rgba(0, 0, 0, 1)">
}
</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Son() {
Person.call(</span><span style="color: rgba(0, 0, 255, 1)">this</span>) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">将person里面的this改成Son里面的this</span>
<span style="color: rgba(0, 0, 0, 1)">}
Son.prototype </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Person()
console.log(</span><span style="color: rgba(0, 0, 255, 1)">new</span> Son().password) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">123</span></pre>
</div>
<h2>三. Javascript面向对象编程:非构造函数的继承</h2>
<h3>1. 什么是"非构造函数"的继承?</h3>
<p>比如,现在有一个对象,叫做"中国人"。</p>
<div class="cnblogs_code">
<pre data-index="38">var Chinese = {
nation: '中国'
};</pre>
</div>
<p>还有一个对象,叫做"医生"。</p>
<div class="cnblogs_code">
<pre data-index="39">var Doctor = {
career: '医生'
}</pre>
</div>
<p>请问怎样才能让"医生"去继承"中国人",也就是说,我怎样才能生成一个"中国医生"的对象?</p>
<p>这里要注意,这两个对象都是普通对象,不是构造函数,无法使用构造函数方法实现"继承"。</p>
<h3>2. object()方法</h3>
<p>json格式的发明人Douglas Crockford,提出了一个object()函数,可以做到这一点。</p>
<div class="cnblogs_code">
<pre data-index="40">function object(o) {
function F() { }
F.prototype = o;
return new F();
}</pre>
</div>
<p>这个object()函数,其实只做一件事,就是把子对象的prototype属性,指向父对象,从而使得子对象与父对象连在一起。</p>
<p>使用的时候,第一步先在父对象的基础上,生成子对象:</p>
<div class="cnblogs_code">
<pre data-index="41">var Doctor = object(Chinese);</pre>
</div>
<p>然后,再加上子对象本身的属性:</p>
<div class="cnblogs_code">
<pre data-index="42"> Doctor.career = '医生';</pre>
</div>
<p>这时,子对象已经继承了父对象的属性了。</p>
<div class="cnblogs_code">
<pre data-index="43"> alert(Doctor.nation); //中国</pre>
</div>
<h3>3. 浅拷贝</h3>
<p>除了使用"prototype链"以外,还有另一种思路:把父对象的属性,全部拷贝给子对象,也能实现继承。</p>
<p>下面这个函数,就是在做拷贝:</p>
<div class="cnblogs_code">
<div class="cnblogs_code_toolbar"> </div>
<pre data-index="44">function extendCopy(p) {
var c = {};
for (var i in p) {
c = p;
}
c.uber = p;
return c;
}</pre>
<div class="cnblogs_code_toolbar"> </div>
</div>
<p>使用的时候,这样写:</p>
<div class="cnblogs_code">
<pre data-index="45">var Doctor = extendCopy(Chinese);
Doctor.career = '医生';
alert(Doctor.nation); // 中国</pre>
</div>
<p>但是,这样的拷贝有一个问题。那就是,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。</p>
<p>请看,现在给Chinese添加一个"出生地"属性,它的值是一个数组。</p>
<div class="cnblogs_code">
<pre data-index="46">Chinese.birthPlaces = ['北京','上海','香港'];</pre>
</div>
<p>通过extendCopy()函数,Doctor继承了Chinese。</p>
<div class="cnblogs_code">
<pre data-index="47">var Doctor = extendCopy(Chinese);</pre>
</div>
<p>然后,我们为Doctor的"出生地"添加一个城市:</p>
<div class="cnblogs_code">
<pre data-index="48">Doctor.birthPlaces.push('厦门');</pre>
</div>
<p>发生了什么事?Chinese的"出生地"也被改掉了!</p>
<div class="cnblogs_code">
<pre data-index="49">alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门
alert(Chinese.birthPlaces); //北京, 上海, 香港, 厦门</pre>
</div>
<p>所以,extendCopy()只是拷贝基本类型的数据,我们把这种拷贝叫做"浅拷贝"。这是早期jQuery实现继承的方式。</p>
<p>4. 深拷贝</p>
<p>所谓"深拷贝",就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难,只要递归调用"浅拷贝"就行了。</p>
<div class="cnblogs_code">
<div class="cnblogs_code_toolbar"> </div>
<pre data-index="50">function deepCopy(p, c) {
var c = c || {};
for (var i in p) {
if (typeof p === 'object') {
c = (p.constructor === Array) ? [] : {};
deepCopy(p, c);
} else {
c = p;
}
}
return c;
}</pre>
<div class="cnblogs_code_toolbar"> </div>
</div>
<p>使用的时候这样写:</p>
<div class="cnblogs_code">
<pre data-index="51">var Doctor = deepCopy(Chinese);</pre>
</div>
<p>现在,给父对象加一个属性,值为数组。然后,在子对象上修改这个属性:</p>
<div class="cnblogs_code">
<pre data-index="52">Chinese.birthPlaces = ['北京','上海','香港'];
Doctor.birthPlaces.push('厦门');</pre>
</div>
<p>这时,父对象就不会受到影响了。</p>
<div class="cnblogs_code">
<pre data-index="53">alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门
alert(Chinese.birthPlaces); //北京, 上海, 香港</pre>
</div>
<p>目前,jQuery库使用的就是这种继承方法。</p>
<h2>四. Javascript面向对象编程:<span class="md-plain md-expand">多态</span></h2>
<h3 class="md-end-block md-heading md-focus"><span class="md-plain md-expand">1.重写:子类重写父类的方法</span></h3>
<h3><span class="md-plain md-expand">2.</span><span class="md-plain md-expand">重载:同一个类里面有多个同名的方法(覆盖 js没有重载)</span></h3>
</div><br><br>
来源:https://www.cnblogs.com/qianduanLamp/p/16596655.html
頁:
[1]