js面向对象(OOP)

我心飞翔 分类:javascript

1、面向对象编程介绍

我们总是听说,这是面向过程的语言,那是面向对象的语言,就是上面是面向对象,什么是面向过程的呢?

1.1 面向过程编程(POP)

面向过程分析解决问题所需要的步骤,然后用函数把步骤一步一步实现,使用的时候再一步一步调用

面向过程就是按照我们分析好的步骤解决问题

如:大象装进冰箱问题

  • 先打开冰箱 -> 把大象装进去冰箱 -> 关闭冰箱

1.2 面向对象编程(OOP)

面向对象:把事物分解成为一个一个对象,然后由对象之间分工与合作

面向对象是以对象功能来划分问题,而不是步骤

同样是装大象问题

  1. 创建大象对象 -> 封装一个 进去 的方法
  2. 创建一个冰箱对象 -> 封装一个 打开关闭 的方法

面向对象的特性:

  1. 封装性:把一些时间封装成一个方法
  2. 继承性:子对象可以继承父对象的特性
  3. 多态性:对象在不同场景下实现不同的功能,多种状态

1.3 面向过程 VS 面向对象

面向过程:

  • 优点:性能比较高,适合跟硬件联系很紧密的东西,例如单片机采用的就是面向过程编程
  • 缺点:没有面向对象易维护、易复用、易扩展

面向对象:

  • 优点:以维护、易复用、易扩展,由于面向对象有封住、继承、多态的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护,更适合人多合作的大型项目
  • 缺点:性能比面向过程低

面向过程就像是自己炒一份蛋炒饭,需要自己买蛋、买米,还要有锅

面向对象就像是到饭店点了一份蛋炒饭

2、ES6中的类和对象

前面我们说过对象,现在来温故一下

对象 一组无序的相关属性和方法的集合,它由 属性方法 组成

  • 属性:即事物的特征
  • 方法:事物的行为

2.1 类 class

ES6中新增了类的概念,可以使用class关键字声明一个类,之后以这个类来实例化对象

所以对象特指某一个,而类则是指一个大类,通过类可以实例化一个具体的对象

如:声明一个明星的类,通过明星这个类可以实例化一个周杰伦(对象)

模型对象的思维特点:

  1. 抽取对象的公共属性和行为封装成一个类
  2. 对类进行实例化,获取类的对象

2.2 创建类(两种方式)

class关键字声明一个类

// 类声明
class Person {
    // class body
}

// 类表达式
var Person = class {}
 
  • 建议类的首字母大写
// 创建实例 
var p = new Person()
 
  • 必须使用new实例对象

类的构成

类可以包括 构造函数实例方法获取函数设置函数静态类方法

class Foo {
    // 构造函数
    constructor() {}

    // 获取函数
    get myName() {}

    // 静态方法
    static myAge() {}
}
 

注意点:

  1. 与函数定义不同,类定义时不能提升的
  2. 函数受函数作用域限制,类受块作用域限制
  3. 默认情况下,类定义中的代码都是在严格模式下执行

2.3 类构造函数 constructor

constructor是类的构造函数(默认方法),用于传递参数,返回实例对象,通过new 命令生成对象实例时,自动调用该方法

如果没有显示定义,类内部会自动为为我们创建一个 constructor(),只不过为空函数而已

class Foo {
    // 构造函数
    constructor(uname, age) {
        this.namer = uname;
        this.age = age
        console.log('实例对象 自动调用');
        console.log(arguments.length);
    }
}
var f = new Foo('张三', 18); // 实例对象时自动调用  2
console.log(f.namer); // 张三
console.log(f.age); // 18
 
  • 类里面有个 constructor() 函数,可以接受传递过来的参数,同时返回实例对象
  • 类实例化时掺入的参数会用作构造函数的参数。如果不需要参数,类名后面的括号其实可以不用加的

实例化操作

使用new 操作符实例化 Foo的操作等于使用new调用其构造函数

使用new调用类的构造函数会执行如下操作:

  1. 在内存中创建一个新对象
  2. 新对象内部的[[ Prototype ]] 指针被赋值为构造函数的 prototype 属性
  3. 将构造函数内部的this指向新对象
  4. 执行构造函数内部的代码(给新对象添加属性)
  5. 最后返回新对象

所以,类的this指向是实例对象

我们用typeof 操作符检测一下类

console.log(typeof Foo);  // function
 

与立即调用函数表达式相似,类也可以立即实例化

new class Foo {
    constructor(x) {
        console.log(x);  // 张三
        console.log(arguments.length);  // 1
    }
}('张三')
 

2.4 在类中添加方法

class Foo {
    constructor(x) {
        this.namer = x
    }
	// 普通方法
    sayHello(hello) {
        console.log(hello + ' ' + this.namer);
    }
}

var f = new Foo('张三')
f.sayHello('Hello');  // Hello 张三
 

静态方法

静态方法通常用于执行不特定实例的操作,也不要求存在类的实例,即可以不用实例化对象,直接调用

静态类方法在类定义的时候使用 static 关键字作为前缀

class Foo {
    constructor(x) {
        this.namer = x
    }
    // 静态方法
    static sayHello(hello) {
        console.log(hello);
    }
}

Foo.sayHello('Hello, 张三'); // Hello,张三
 

2.5 设置和获取函数

语法和普通函数一样,只不过设置函数是来获取类实例对象的时候传进来的参数,而获取函数是将这些参数拿来用的

class Foo {
    set namer(newName) {
        this.newName = newName
    }

    get namer() {
        return this.newName + '不爱李四';
    }
}

var f = new Foo()
f.namer = '张三'
console.log(f.namer);  // 张三不爱李四
 

3、类的继承

3.1 初始继承

程序中的继承:子类可以继承父类的一些属性和方法

使用 extends 关键字,就可以继承任何拥有[[ Constructor ]] 和原型的对象

语法:

// 父类
class Pather() {}

// 子类
class Son extends Pather {}
 

栗子:

class Pather {
    constructor() {}
    say() {
        console.log('你好哈');
    }
}
// 继承父类Pather
class Son extends Pather {}

var son = new Son()
son.say();  // 你好哈
 

extends关键字也可以用在类表达式中,因此,var son = class extends Pather {} 也是可以的

3.2 super关键字

super 关键字用于访问和调用父类上的函数可以调用父类的构造函数,也可以调用父类的普通函数,同样也可以继承静态方法

1、调用父类的构造函数

class Pather {
    constructor(namer, age) {
        this.namer = namer;
        this.age = age;
    }

    say() {
        return this.namer + this.age + '岁了';
    }
}

class Son extends Pather {
    constructor(namer, age) {
        // 调用父类中的构造函数
        super(namer, age)
    }
}

var son = new Son('张三', 2)
console.log(son.say());  // 张三2岁了
 
  • 在父类中,say 中的 this.namerthis.age 指向的是父类的构造函数,所以子类需要添加 super() 关键字调用父类中的构造函数,这样子类就继承了父类的方法了

就近原则

在继承中,实例化子类输出一个方法,先看子类中有没有这个方法,有就直接返回,没有再去父类中寻找

class Father {
    say() {
        console.log('我是父亲');
    }
}

class Son extends Father {
    say() {
        console.log('我是儿子');
    }
}
var son = new Son();
son.say();  // 我是儿子
 

2、调用普通函数

super() 也可以用来调用普通函数,即使子类也封装了跟父类一样的方法

class Pather {
    say() {
        return '我是父亲'
    }
}

class Son extends Pather {
    say() {
        return super.say() + '的儿子'
    }
}

var son = new Son()
console.log(son.say());  // 我是父亲的儿子
 

3、调用静态方法

class Pather {
    static say() {
        console.log('调用父类静态方法');
    }
}

class Son extends Pather {
    static show() {
        super.say()
    }
}

Son.show(); // 调用父类静态方法
 

使用super有几个注意事项:

1、super 只能在子类构造函数和静态方法中使用

2、不能单独使用super关键字,要么用它调用构造函数,要么用它引用静态方法

// 报错
class Son extends Pather {
	constructor() {
        console.log(super)
    }
}
 

3、调用 super() 会调用父类构造函数,并将返回的实例赋值给this

class Son extends Pather {
	constructor() {
		super()
		console.log(this instanceof Pather);  // true
    }
}
 

4、在类构造函数中,不能在调用super 之前引用 this (必须先调用父类的构造函数,在调用子类的构造方法)

// 报错
class Son extends Pather {
	constructor() {
		console.log(this);
        super()
    }
}
 

5、如果在子类中显示定义了构造函数,则要么必须在其中调用 super,要么必须在其中返回一个对象

// class Son extends Pather {
// 	constructor() {
//         super()
//     }
// }

class Son extends Pather {
	constructor() {
        return {}
    }
}
 

3.3 继承内置类型

继承js的内置类型数组

class SuperArray extends Array {
    shuffle() {
        // 洗牌算法  随机两张牌互换
        for (let i = this.length - 1; i > 0; i--) {
            // 随机生成一个0~4之间的整数
            let j = Math.floor(Math.random(i + 1));
            [this[i], this[j]] = [this[j], this[i]]
        }
    }
}

// 继承数组,传入的参数自然被数组对象转换成数组
let a = new SuperArray(1, 2, 3, 4, 5, 6)
console.log(a instanceof Array); // true
console.log(a instanceof SuperArray); // true
console.log(a instanceof Object); // true

console.log(a);  // [ 1, 2, 3, 4, 5, 6 ]
// 洗牌
a.shuffle()
console.log(a);  // [ 2, 3, 4, 5, 6, 1 ]
 

4、注意事项

constructor 里面的 this 指向是实例对象,方法里面的 this 指向的是它的调用者

<body>
    <button>按钮</button>
    <script>
      	// 封装一个类 点击按钮弹出信息
        class Foo {
            constructor(namer, age) {
                this.namer = namer;
                this.age = age;
                this.btn = document.querySelector('button');
                // 点击按钮的时候,调用show函数
                // 注意:this.show 后面不能加括号,否则实例对象会立即执行
                this.btn.onclick = this.show
            }
            show() {
                console.log('点击按钮');
                console.log(this.namer);
            }
        }

        var f = new Foo()
    </script>
</body>
 

你猜结果会是什么?

可能没想到,结果是 “点击按钮” 和 undefined,很明显,undefinedconsole.log(this.namer); 打印出来的

实际情况是这样的:

constructor() 中的 this 指向的是实例对象 f,而**show() 中指向的是它的调用者(按钮btn)**,由于按钮btn中没有 namer 属性,所以返回 undefined

解决办法:让 show() 中的 this 是指向实例对象

var that;
class Foo {
    constructor(namer, age) {
        // 保存this到that
        that = this;
        this.namer = namer;
        this.age = age;
        this.btn = document.querySelector('button');
        this.btn.onclick = this.show
    }
    show() {
        console.log(that.namer + '点击按钮');
        console.log(this);  // 按钮事件
    }
}

new Foo('张三', 2)
 

总结:

面向对象.png

回复

我来回复
  • 暂无回复内容