JavaScript学习笔记(十一):面向对象编程

一、关于面向对象

编程可以分为面向过程编程和面向对象编程
面向过程:就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用
面向对象:把一件事分解成一个个对象,然后由对象之间分工合作。面向对象是以对象功能来划分问题,而不是步骤。

对象工厂

当需要创建具有相同结构的多个对象时,采用对象字面量或者new Object将很难适应,因此采用对象工厂模式来创建一个对象

{
	// 很繁琐
	let tom = {
		name:"Tom",age:20,sex:"male"
	}
	let jerry = {
		name:"Jerry",age:18,sex:"female"
	}
}
{
	// 对象工厂函数
	function createPerson(name,age,sex){
		return {name,age,sex}
	};
	let tom = createPerson("Tom",20,"male");
	console.log(tom); // { name: 'Tom', age: 20, sex: 'male' }
	let jerry = createPerson("Jerry",18,"female");
	console.log(tom == jerry); // false
}

对象工厂也有问题,对象工厂本身是一个普通函数,用于表达对象结构时,描述性不强。对象工厂没有解决对象标识的问题,即创建的对象是什么类型,利用构造函数就可以解决对象工厂的问题

构造函数

构造函数的首字母要大写,与普通函数作为区分。
其工作原理:

  1. 在内存中创建一个新对象
  2. 这个新对象内部的[[Prototype]]特性被赋值为构造函数的prototype属性
  3. 构造函数内部的this被赋值为这个新对象(即this指向新对象)
  4. 执行构造函数内部的代码(给新对象添加属性)
  5. 如果构造函数返回非空对象,则返回该对象,否则返回刚创建的新对象(所以构造函数不需要return)
{
	// 创建与使用
	function Person(name,age){
		this.name = name;
		this.age = age;
		this.show = function(){
			console.log(`hello:${this.name}`);
		}
	}
	let tom = new Person("tom",20);
	let jack = new Person("Jack",22);
	tom.show(); // hello:tom
}

构造函数用于创建特定类型的对象,如Object和Array等,以函数的形式为自己的对象类型定义属性和方法。
上面的例子中,tom和jack分别保存着Person的不同实例,这两个对象都有一个constructor属性指向Person。

{
	console.log(tom.constructor == Person); // true
    console.log(jack.constructor == Person); // true
}

constructor是用于标识对象类型的,不过一般使用instanceof操作符来确定对象的类型,所有自定义对象都继承自Object

{
	console.log(tom instanceof Person); // true
	console.log(tom instanceof Object); // true
}

构造函数的问题

构造函数虽然有用但也不是没有问题,主要问题在于其定义的方法会在每个实例上都创建一遍,因此对于刚刚的例子来说,每次实例化对象时都会创建show方法,但这个两个实例对象不是相等的,因此方法也不等,但是要做的事却是一样的,因此没必要定义两个不同的方法实例。解决这个问题可以通过把函数定义转移到构造函数外面。

{	
	function Person(name,age){
		this.name = name;
		this.age = age;
		this.show = show
	}
	function show(){
		console.log(`hello:${this.name}`)
	}
	let tom = new Person("tom",20);
	let jack = new Person("Jack",22);
	tom.show(); // hello:tom
	// 不是相等的,因此显得冗余
	console.log(tom.show == jack.show); // false
}

show函数被定义到了全局,在构造函数内部show属性等于全局的show函数,所以构造函数的实例对象共享了在全局上的show函数,这样虽然解决了重复的问题,但是全局作用域也被混乱了,因为show函数只能在实例对象上调用,如果构造函数有多个方法那么就要写多个全局函数,显得很麻烦,因此可以通过原型模式来解决。

原型模式

每一个函数都有一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上这个对象就是通过调用构造函数实例化的对象的原型。因此可以使用这个原型对象来定义属性或者方法,这样就可以被构造函数对象实例所共享。

{
	function Person(name,age){
		this.name = name;
		this.age = age;
	}
	Person.prototype.grade = 'grade one';
	// constructor属性指回构造函数
	console.log(Person.prototype.constructor == Person); // true
}

在这里插入图片描述

{
	// 构造函数的创建与使用
    function Mouse(name,age){
        this.name = name;
        this.age = age;
    }
    
	Mouse.prototype.from = 'Cartoon'; // 都会被继承
    let jerry = new Mouse('Jerry',19);
    let mickey = new Mouse('Mickey',20)
    
    // 当在构造函数原型上创建属性(或方法)时,会被改构造函数的额所有对象所共享。
    console.log(jerry.from); // Cartoon
    console.log(Object.getPrototypeOf(jerry) == Mouse.prototype); // true,同一个原型
    console.log(Object.keys(jerry)); // keys返回可枚举的自己的属性。["name","age"]
    console.log(Object.getOwnPropertyNames(jerry)); // 跟上面一样
    console.log(jerry.hasOwnProperty("from")); // false,判断不了继承的属性
    console.log('from' in jerry); // in判断可枚举的所有属性(包括继承属性)因此为true

    // 一旦对象自身对继承自原型的属性赋值,则创建了一个属于自己的同名属性,并覆盖了继承自原型的属性。
    mickey.from = 'Disney';
    console.log("Jerry From:",jerry.from); // Jerry From: Cartoon
    console.log("Mickey From:",mickey.from); // Mickey From: Disney
}

构造函数、原型与实例对象的关系

  • 每个构造函数都有一个原型对象
  • 原型有一个属性指回构造函数(constructor)
  • 实例对象有一个内部指针[[Prototype]]指向原型

原型链

  • 当对象原型是另一个构造函数的实例,如此迭代,形成了一连串的继承关系就是原型链
  • 原型链表达了对象与对象之间的继承关系

原型链的问题

{
    function Animal() {
      this.colors = ["white", "black"];
    }
    function Mouse(name, age) {
      this.name = name;
      this.age = age;
    }
    // 强制指定原型对象,表达继承关系
    Mouse.prototype = new Animal(); // 从Mouse的原型上继承animal构造函数的实例
    let m1 = new Mouse("Mickey", 10);
    console.log(m1.name, m1.colors);
    m1.colors.push("red");
    let m2 = new Mouse("Miney", 9);
    console.log(m2.colors); // [ 'white', 'black', 'red' ],收到了影响
}

问题在于当原型中包含引用值时(如数组),在各实例间共享的是该引用值的引用。当某个实例修改该属性时会影响全部实例。并且子类在实例化时不能给父类传递参数

盗用构造函数:在子类构造函数中调用父类构造函数,并将子类当前实例指定为构造函数的上下文。

{
	function Animal(type){
        this.colors = ['white','black'];
        this.type = type
    }
    function Mouse(name,age,type = 'Mouse'){
    	// 把父构造函数的this通过call改为当前构造函数的this
        Animal.call(this,type); // 父类构造函数的盗用,解决传参的问题
        this.name = name;
        this.age = age;
    }
    Animal.prototype.show = function(){
        console.log(this.type,this.colors);
    }
    // 强制指定原型对象,表达继承关系
    Mouse.prototype = new Animal(); // 从Mouse的原型上继承animal构造函数的实例
    Mouse.prototype.constructor = Mouse; // 强制指定构造函数为原构造函数
    
    let m1 = new Mouse('Mickey',10);
    m1.show(); // 就是Animal上面的show方法,this就为当前生成的实例对象的this Mouse [ 'white', 'black' ]
    m1.colors.push('red'); 
    console.log(m1.name,m1.type,m1.colors); // Mickey Mouse [ 'white', 'black', 'red' ]

    let m2 = new Mouse('Duck',9);
    m2.show();
    m2.colors.push('pink');
    console.log(m2.name,m2.type,m2.colors); // Duck Mouse [ 'white', 'black', 'pink' ]

    console.log(m1 instanceof Mouse); // true
    console.log(m1 instanceof Animal); // true
    console.log(Object.keys(m1)); // [ 'colors', 'type', 'name', 'age' ]
    console.log(Mouse.prototype.isPrototypeOf(m2)); // true

    console.log(m1.constructor == Mouse); // true
    console.log(m1.constructor == Animal); // false
}

二、ES6新增的class关键字

ES6中新引入了class关键字具有正式定义类的能力,class是一种新的语法糖可以显式的用来创建一个类,虽然表面上看起来可以支持正式的面向对象编程,但实际上它背后使用的仍然是原型和构造函数的概念

{
	// ES5定义类和继承
    function Car(title){
        this.title = title;
    }
    
    Car.prototype.drive = function(){
        return 'Venoom';
    }
    
    const car = new Car("BMW");
    console.log(car.title); // BMW
    console.log(car.drive()); // Venoom
    
    // ES5实现继承
    function Benz(color,title){
        Car.call(this,title); // 把当前对象作为this,改变父构造函数的this指向
        this.color = color;
    }
    
    Benz.prototype = Object.create(Car.prototype);
    Benz.prototype.constructor = Benz;
    
    const ben = new Benz("red","focus");
    console.log(ben.title); // focus
    console.log(ben.drive()); // Venoom
}
{
	// ES6定义类和继承
	class People{
	    constructor(options){ 
	        this.name = options.name; 
	    }
	    say(){
	        return 'hello';
	    }
	}
	// ES6的继承,使用extends关键字
	class Boy extends People{
	    constructor(options){
	        super(options);
	        this.age = options.age;
	    }
	}
	const boy = new Boy({name:'小李',age:20})
	console.log(boy.name); // 小李
	console.log(boy.say()); // hello
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值