JavaScript面向对象编程-对象的继承

对象的继承:通过原型对象继承;基于class继承。

// 这里只介绍通过原型对象继承

原型对象的概述

构造函数的缺点

function Person(name, age) { // 所有实例对象都会生成name age 属性 和say方法
  this.name = name;
  this.age = age;
  this.say = function() {
    console.log("Hello world!");
  };
}

let per1 = new Person("中小余", 18);
let per2 = new Person("中小鱼", 17);
per1.say === per2.say; // false  同一个构造函数的多个实例之间无法共享属性,两个实例会生成两个同样的say方法,浪费系统资源,解决这个问题的方法就是“原型对象prototype”

prototype属性的作用

JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。

原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象

- 每一个函数都有一个prototype属性,指向一个对象
	// 对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型
    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    Person.prototype.say = function() {
      console.log("Hello world!");
    };

    let per1 = new Person("中小余", 18);
    let per2 = new Person("中小鱼", 17);
    per1.say === per2.say; //true
    per1.say(); //'Hello world!'
    per2.say(); //'Hello world!'

-  原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
	
	Person.prototype.say = function() {
      console.log("我是一只酸菜鱼,又酸又菜又多余!");
    };
    per1.say(); // '我是一只酸菜鱼,又酸又菜又多余!'
    per2.say(); //'我是一只酸菜鱼,又酸又菜又多余!'

- 如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。如果实例对象上没有某个属性和方法的时候,它会到原型对象上去寻找该属性或方法。
	
	function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    Person.prototype.say = function() {
      console.log("Hello world!");
    };

    let per1 = new Person("中小余", 18);
    let per2 = new Person("中小鱼", 17);
    per1.say = function() {
      console.log("我是一只酸菜鱼,又酸又菜又多余");
    };
    per1.say(); // '我是一只酸菜鱼,又酸又菜又多余'  他自身有say方法
    per2.say(); // 'Hello world!' 他自身没有say方法 所以会去原型上找

原型链

  • 所有的对象都有自己的原型对象
    • ​ 一方面任何一个对象,都可以充当其它对象的原型
    • ​ 另一方面由于原型对象也是对象,所以它也有自己的原型,因此会形成一个“原型链”(对象到原型,再到原型的原型….)
      • 一层层往上追溯,所有对象的原型最终都可以追溯到Object.prototype,即Object构造函数的prototype属性。也就是说,所有对象都继承了Object.prototype的属性,这就是所有对象都有valueOftoString方法的原因,因为这是从Object.prototype继承的。
      • Object.prototype也有自己的原型,它的原型是nullnull没有任何的属性和方法,也没有自己的原型,因此原型链的尽头就是null
      • Object.getPrototypeOf(对象名):返回参数对象的原型

constructor属性

prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.constructor === Person;// true

constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承

function Person(name, age) {
  this.name = name;
  this.age = age;
}
let per1 = new Person("中小鱼", 18);
per1.constructor === Person; // true
per1.constructor === Person.prototype.constructor; //true
per1.hasOwnProperty("constructor");//false


constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。

function Person(name, age) {
  this.name = name;
  this.age = age;
}
function Man(name, age) {
  this.name = name;
  this.age = age;
}
let per1 = new Person("中小鱼", 18);
per1.constructor === Person; // true
per1.constructor === Man; //false

有了constructor属性,就可以从一个实例对象新建另一个实例

function Person(name, age) {
  this.name = name;
  this.age = age;
}
let per1 = new Person("中小鱼", 18);
let per2 = new per1.constructor();

per2.constructor === Person; // true
per2 instanceof Person; //true

constructor属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,一般会同时修改constructor属性,防止引用的时候出错。

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.constructor === Person; // true
Person.prototype = {
  say: function() {
    console.log("hello world!");
  }
};
Person.prototype.constructor === Person;// false  修改了原型对象但是没有改constructor

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.constructor === Person; // true
Person.prototype = {
  constructor: Person,// 修改constructor的指向
  say: function() {
    console.log("hello world!");
  }
};
Person.prototype.constructor === Person; //true

/
// 另一种写法
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.constructor === Person;//true
Person.prototype.say = function() {// 另一种写法:只在原型对象上添加方法
  console.log("hello world!");
};
Person.prototype.constructor === Person; //true


instanceof 运算符

- 返回一个布尔值 ,表示对象是否为某个构造函数的实例
    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    let ins = new Person();
    ins instanceof Person; // true

- 它会检查右边构造函数的原型对象(prototype)是否在左边对象的原型链上,因此还可以这么写
	
	Person.prototype.isPrototypeOf(ins); // true
	// isPrototypeOf()方法是 JavaScript 提供的原生方法,用于检查某个对象是否为另一个对象的原型

- 由于instanceof检查整个原型链,因此同一个实例对象,可能会对多个构造函数返回true
	 
    let ins = new Person();
    ins instanceof Person; // true
    ins instanceof Object; // true

- 由于任意对象都是Object的实例(除了null),除了null其他对象的instanceOf Object的运算结果都是true,
所以instanceof运算等你可以判断一个值是否为非null的对象
	ins instanceof Object;// true
	null instanceof Object; // false

- instanceof 判断失真的情况
	let obj = Object.create(null);
    typeof obj; //'object'
    obj instanceof Object; //false
	//instanceof的原理是检查右边构造函数的prototype属性,是否在左边对象的原型链上。 
	// 但左边对象的原型链上,只有null对象。这时,instanceof判断会失真。

- instanceof用来判断值的类型
	let arr = [1, 2];
    let obj = {};
    let str = "string";

    arr instanceof Array; // true
    obj instanceof Object; // true
    str instanceof String; //false  instanceof运算符只能用于对象,不适用原始类型的值
    undefined instanceof Object; // false
    null instanceof Object; // false  undefined和null,instanceof运算符总是返回false
- 可以解决调用构造函数时,忘记加new命令的问题
    function Fubar (foo, bar) {
      if (this instanceof Fubar) {
        this._foo = foo;
        this._bar = bar;
      } else {
        return new Fubar(foo, bar);
      }
    }

构造函数的继承

让一个构造函数继承另一个构造函数,分成两步实现:
	- 1.在子类的构造函数中,调用父类的构造函数
	- 2.让子类的原型指赂父类的原型,这样子类就可以继承父类原型
    
    function Parent() {
      this.x = 0;
      this.y = 0;
    }
    Parent.prototype.say = function(x, y) {
      this.x += x;
      this.y += y;
      console.log("Parent.say");
    };
    // 第一步:子类继承父类的实例
    function Sub() {
      Parent.call(this); //在子类的构造函数中,调用父类的构造函数
    }
    // 另一种写法
    function Sub() {
      this.legacy = Parent;
      this.legacy();
    }

    // 第二步:子类继承父类的原型
    Sub.prototype = Object.create(Parent.prototype);
    /* sub.prototype是子类的原型,要将它赋值为Object.create(Parent.prototype);
    而不是直接赋值为 Parent.prototype,因为以这种方式赋值对sub.prototype做操作的话也会修改父的的原型
    */
    Sub.prototype.constructor = Sub; // 修改constructor属性的指向

    let ins = new Sub();
    ins instanceof Sub; // true
    ins instanceof Parent; //true
	ins.say(); //'Parent.say'

多重继承

JS中不允许一个对象同时继承多个对象,但是可以通过Mixin(混入)模式实现。

function Fun1() {
  this.name = "中小余";
}
function Fun2() {
  this.age = 18;
}
function Say() {
  Fun1.call(this);
  Fun2.call(this);
}
//继承fun1
Say.prototype = Object.create(Fun1.prototype);
//继承链上加入fun2
Object.assign(Say.prototype, Fun2.prototype);
//指定构造函数
Say.prototype.constructor = Say;

let talkAbout = new Say();
console.log(talkAbout.name + "就是" + talkAbout.age + "岁"); //'中小余就是18岁'

模块

基本的实现方法

- 模块是实现特定功能的一组属性和方法的封装
	let comp = new Object(
    	name:'中小余',
        age:18,
        say:function(){
        	//...
   		 },
         do:function(){
             // ...
         }
      
    );
comp.say();//调用对象的方法
// 这种方法会暴露所有模块成员,内部状态还可以被外部改写

还可以利用构造函数,封装私有变量

function Person() {
  this.buffer = [];
}
Person.prototype = {
  contructor: Person,
  say: function(val) {
    this.buffer.push(val);
  },
  toString: function() {
    return this.buffer.join("");
  }
};

let yu = new Person();
yu.say(1);
yu.buffer;//[1]
yu.toString();// '1'

yu.buffer = [3, 4, 5, 6, 7]; // 外部能改变私有变量
yu.say(1);
yu.toString();// '345671'

封装私有变量:立即执行函数

解决暴露私有成员的问题

 let person = (function(){
  let str0  = '小余要坚强鸭';
  let str1 = '小余好南';
  let fun1 = function(){
    console.log(str1)
  };
  let fun2 = function(){
    console.log(str0); 
  };
  return function(){
    this.fun1 = fun1;
    this.fun2 = fun2
  };
})()

let yu = new person();
yu.fun2(); // '小余要坚强鸭'
yu.str0 = '小余坚强';
yu.fun2(); // '小余要坚强鸭'  str0没有变改变

模块的放大模式

如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用“放大模式”(augmentation)。

var module1 = (function (mod) {
 //...
 return mod;
})(window.module1 || {});

输入全局变量

为了在模块内部调用全局变量,必须显式地将其他变量输入模块。

(function($, window, document) {

  function go(num) {
  }

  function handleEvents() {
  }

  function initialize() {
  }

  function dieCarouselDie() {
  }

  //attach to the global scope
  window.finalCarousel = {
    init : initialize,
    destroy : dieCarouselDie
  }

})( jQuery, window, document );
// finalCarousel对象输出到全局,对外暴露init和destroy接口,内部方法go、handleEvents、initialize、dieCarouselDie都是外部无法调用的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

中小余

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值