js——面向对象

本文详细介绍了JavaScript中的面向对象编程,包括ES5和ES6的实现方式。探讨了原型对象、原型链、继承的各种模式,如原型链继承、构造函数继承、组合继承等,并讨论了ES6中的class和继承特性,以及静态方法和多态的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

面向对象编程的模式。面向对象的概念是程序以对象为主,任何属性和方法都是由对象发起。对象之间可以有继承关系。

基本要素:对象、类(对象的模板)。

面向对象的核心特征:封装、继承、多态(子类继承父类之后,对父类的属性或方法做了改写)。

js万事万物皆对象,但是js不是一个真正的面向对象语言,因此,对象的模板不是类,而是(构造函数)。

封装:我们把与对象有关的数据(原始数据、函数等)打包到一个作用域中,打包的过程就是封装。

继承:对象之间可以有上下级关系。

多态:父亲的音乐天赋非常好,儿子继承了父亲的音乐天赋。父亲对古典音乐比较擅长,儿子流行音乐比较擅长。多个儿子继承来的都会不同。

es5 面向对象

es5 使用 构造函数实现对象的原型。

    //构造函数
    function car(name) {
      this.name = name;
      let x = 1;
      let y = 2;
      this.c = x + y;
      this.run = function () {
        console.log(this.name + '跑起来了');
      }
    }

    const a = new car('兰博基尼');

    console.log(a);
    a.run()

原型对象和原型链

任何对象必然都是由一个构造哈桑农户来生成(new)的。

任何对象(除了对象原型)都继承自上级对象。

实例对象.__proto__指向原型对象。

实例对象.__proto__ == 构造函数.prototype 结果是true。

所有引用类型(函数,数组,对象)都拥有__proto__属性(隐式原型)。

所有函数除了有__proto__属性(隐式原型)之外还拥有prototype属性(显式原型)。

函数的prototype属性(显式原型)与 对象的__proto__属性(隐式原型)都指向 原型对象,所以

对象的__proto__与构造函数的prototype相等。

构造函数通过new生成实例化对象。

实例化对象的属性__proto__指向 原型对象。

原型对象的属性 constructor 指向构造函数。

构造函数的属性prototype指向原型对象。

继承

一共6种方式:

1、原型链继承

    function 父() {
      this.长相 = { 曾经: '帅' };
    }
    function 子() {
      this.名字 = '张三';
    }
    子.prototype = new 父();

    const 张三 = new 子();
    const 李四 = new 子();

    李四.长相.曾经 = '丑';
    console.log(张三.长相.曾经)

子类的实例对象在获取属性时,如果找不到,会去子类的原型对象中查找,再找不到,会根据原型链去上级的实例对象(属性必须在执行上下文中性存在才有意义)中查找。

原型链的继承本质是:将子类的原型对象改变为父类的实例对象。

我没有的属性,应该在我的祖先中查找,如果将我的祖先换成别人。那么,别人及别人的祖先的属性,我就都可以用了。

缺点是:多个实例继承的是同一个原型,任何一个实例改变了原型,其他实例也会跟着变。

2、构造函数继承(借助 call)

    function 父() {
      this.长相 = { 曾经: '帅' };
    }
    function 子() {
      父.call(this);
      this.名字 = '张三';
    }

    父.prototype.abc = 123;

    const 张三 = new 子();
    const 李四 = new 子();

    李四.长相.曾经 = '丑';

    console.log(张三.abc)//报错

使用call或apply或bind来改变父类的this为子类的this后,在子类中执行父类的构造过程。可以将父类的属性构造在子类中。

缺点:call只是调用了父亲的构造函数,因此,对于父类的原型对象上的属性无法继承的。

3.组合继承(前两种组合)

    function 父() {
      this.长相 = { 曾经: '帅' };
    }
    function 子() {
      父.call(this);
      this.名字 = '张三';
    }

    父.prototype.abc = 123;

	//将儿子的原型对象指向父亲
    子.prototype = new 父();

	//因为构造函数与原型对象之间通过prototype和constructor互相指向,
	//我们将子类的原型对象变成了父类的实例对象,而实例对象无法通过constructor返回构造函数。
	//所以我们强行指定父类的实例对象的constructor指向子类的构造函数(形成回路)
    子.prototype.constructor = 子;

    const 张三 = new 子();
    const 李四 = new 子();

    李四.长相.曾经 = '丑';

    console.log(张三.abc)

好处是:

初始属性被继承到自身,所以,单个实例对象直接初始属性是独立的,互相没有练习。

结合原型链继承,多个实例之间也有共同的父对象(父的实例对象),如果未来需要同时为多个实例附加新的属性或方法时,方法都可以找到父对象来附加。

4.原型继承

    const 父 = {
      长相: { 曾经: '帅' }
    }
    父.__proto__.abc = function () {
      return 123
    }

	//Object.create(父);创建一个新的对象,将新对象继承自父(实例对象)。
    const 张三 = Object.create(父);
    const 李四 = Object.create(父);

    李四.长相.曾经 = '丑'

    console.log(张三.abc())

Object.create这个方法可以实现普通对象的继承,不仅仅能继承属性,同样也可以继承方法。

缺点:多个实例继承的是同一个对象,任何一个实例改变了父对象,其他实例的父对象也会跟着变。

5.寄生式继承(以工程模式实现(原型继承4))

    const 父 = {
      长相: { 曾经: '帅' }
    }
    父.__proto__.abc = function () {
      return 123
    }

	//工厂模式
	//生成一个新对象,让这个新对象继承参数对象,做处理,最后返回。
    function clone(obj) {
      const clone = Object.create(父);
      clone.abc = 123;
      return clone;
    }

    const 张三 = clone(父);
    const 李四 = clone(父);

    李四.长相.曾经 = '丑'

    console.log(张三.abc())

优缺点与4一致,只是写法上有改变,由哈桑农户返回新继承来的对象。

为什么要有寄生式继承,因为工程模式能够很好的的将继承后的子对象做二次处理,且封装,利于阅读。

6.寄生组合式继承

 function 父() {
      this.长相 = { 曾经: '帅' }
    }

    父.prototype.abc = function () {
      return 123
    }

    function 子() {
      父.call(this);
    }

    clone(父, 子);
    function clone(父, 子) {
      //因为没有父类的实例对象,所以创造了一个全新对象(没有必要创建父类的实例对象)
      子.prototype = Object.create(父.prototype);
      子.prototype.constructor = 子;
    }

    const 张三 = new 子();
    const 李四 = new 子();

    李四.长相.曾经 = '丑'

    console.log(张三.abc())

结合第四种中提到的继承方式,解决普通对象的继承问题的Object.create方法,我们在前几种继承方式的优缺点基础上进行改进,得出寄生组合式的继承方式,在es5中这也是所以继承方式里面相对最优的继承方式。

es6面向对象

工厂模式

在实际工作中,通常创建对象时,需要附加一些默认的属性或方法,因此:


    function createObject(name) {
      //可以直接创建对象,也可以做继承。
      const obj = {};
      //创建完对象,可以对对象做进一步处理
      obj.name = name;

      return obj;
    }

	const obj = createObject('张三');

如果需要为对象附加方法,通常采用如下方式:

    function createObject(name) {
      //可以直接创建对象,也可以做继承。
      const obj = {};
      //创建完对象,可以对对象做进一步处理
      obj.name = name;
      obj.eat = eat;

      return obj;
    }
	function eat(){
        
    }

	const obj = createObject('张三');

或者,直接采用构造函数

    function createObject(name) {
      this.name = name;
    }

    createObject.prototype.eat = function () {

    }

    const obj = new createObject('张三');

通常,为对象附加的函数(方法),都是多个对象只需要公用一个函数就可以,因此,可以将函数附加到对象的原型对象上。

es6使用class关键字从语法上模拟了真正面向对象的语法。

要产生继承 必须有一个模板

    class car {
      //构造器
      constructor(name) {
        //用this暴漏的变量叫做公共变量,没有的就私有变量
        this.name = name;
        let a = 1;
        let b = 2;
        this.c = a + b;
        //构造器本身是函数,返回值如果不是对象或没有返回值则不会对结果造成影响。
        //但是如果返回值是对象,则返回值的对象会覆盖原有的对象作为new的结果。
        // return new Date();
      }

      run() {
        console.log(this.name + '这辆车开起来了');
      }

      zhuang() {
        console.log(this.name + '这辆车撞人了');
      }

    }

    const a = new car('a');
    const b = new car('b');

    console.log(a);
    a.run();
    b.run();

es6通过class关键字来定义一个类。

类的定义中有一个constructor函数,这是整个类的构造器。负责初始化对象及接受参数。

constructor中定义属性,使用this属性名称将属性暴露。

类的方法不需要有function关键字,而且函数之间不能用逗号分隔符。

使用new 关键字(实例化对象)将一个类实例化为对象。

继承

    class myCar extends car {
      constructor(n) {
        //引用主类的构造器
        //super应该在自身属性定义前执行
        super(n)
      }
    }

    const a = new myCar('兰博基尼');

    console.log(a);
    a.run()

es6使用extends来继承。

为了实现继承,实例化时,传入参数首先被子类的constructor执行,但是,从语法上,应该时主类的constructor先执行,因此引入super()方法来调用主类的constructor。

静态方法和静态属性

静态方法,如果为一个类的方法(函数)前增加 static关键字,表示当前方法是当前类的静态方法,不会被继承。

    class 富一代 {
      constructor() {
      }
      static game() {
        console.log('打高尔夫');
      }
    }

    class 富二代 extends 富一代 {
      constructor(name, value) {
        super();
      }
    }

    const 思聪 = new 富二代('张三', 123);

	思聪.game();//报错
    富一代.game();//打高尔夫

es的class中,目前还不能对属性添加静态关键字:

      constructor(value) {
        static this.money = value;//报错
      }

折中的使用如下方法来变相实现静态属性:

class 富一代 {
      constructor(value) {
        //将this直接替换为class名称
        富一代.money = value;
      }
    }
    class 富二代 extends 富一代 {
      constructor(name, value) {
        super(123);
        this.name = name;
      }
    }
	const 聪 = new 富二代('张三', 123);

	console.log(聪.money);//undefined
	console.log(富一代.money);//123

多态

   class car {
      //构造器
      constructor() {
        //用this暴漏的变量叫做公共变量,没有的就私有变量
        this.name = '';
      }

      run() {
        console.log(this.name + '这辆车开起来了');
      }

      zhuang() {
        console.log(this.name + '这辆车撞人了');
      }
    }

    class myCar extends car {
      constructor(n) {
        //引用主类的构造器
        super()
        this.name = n;
      }
    }

    const a = new myCar('兰博基尼');
    const b = new myCar('法拉利');

    a.run();//兰博基尼这辆车开起来了
    b.run();//法拉利这辆车开起来了

不同的对象执行相同的方法,产生不同的结果,叫做多态。

虽然run方法定义在主类,但是其内需要使用name属性时,优先查找的却是字类,因为对象是子类实例化的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值