八--JS作用域、this和原型链(二)

一、为什么要面向对象?

面向对象(OOP),对象就是一个容器,一般是类,它是面向对象编程的最小单元,面向对象编程的优点是逻辑迁移灵活,代码可复用性高,高度模块化。对象是一个容器,里边封装了方法和属性。方法为对象行为,属性为对象状态

//简单对象
const course = {
	teacher: '云隐',
    leader: '黄小杨',
    startCourse: function(name) {
      return `开始${name}`;
    }
}
//函数对象
function course(){
	this.teacher='云隐';
    this.leader='黄小杨';
    this.tartCourse=function(name) {
      return `开始${name}`;
    }
}

二、几种生成对象方式
1、通过构造函数来生成对象
生成对象需要一个模版,表征了一类物体的共同特征。类即对象模板,js本质不是基于类,而是基于构造函数和原型链----constructor+prototype

function Course() {
  this.teacher = '云隐',
  this.leader = '黄小杨',
}
const course = new Course();

1)、Course就是构造函数
2)、函数内部的this表示要生成的实例
3)、生成对象需要new来实例化
4)、可以做初始传参
构造函数不初始化,生成的实例无法使用
以下是兼容没有实例化的调用

function Course() {
	if(!(this instanceof Course)){
		return new Course(); 
	}
  this.teacher = '云隐',
  this.leader = '黄小杨',
}
const course = Course();//这样调用这个方法一定是实例化后的对象

2、object.create

const foo = Object.create({})

代表foo对象的原型指向{},foo.proto === {},foo.proto.proto === Object.prototype
3、字面量赋值

const bar = {}

代表bar的原型链指向了Object的原型,bar.proto === Object.prototype
三、原型链
1、new是什么,new做了什么

function Course() {}
  const course = new Course();

1)若方法没有返回一个对象,则创建一个空对象作为返回的对象实例。若有则返回这个对象,这个对象是没有构造器的。
2)将当前实例对象赋值给内部this
3)将返回的空对象的原型对象指向了构造函数的prototype属性,即course.proto == Course.prototype。若返回不是空对象,这个对象的原型对象不指向构造函数原型链
4)执行构造函数初始代码
实例属性之间会互相影响吗?-----不会
new 的实现:

function newFunc(Father){
	var obj = Object.create(Father.prototype);
	const result = Father.apply(obj,Array.prototype.slice.call(arguments, 1));
	return result && typeof result === 'object' ? result:obj;
}

用new 的方式模拟实现Object.create

function inherit(p){
	function f(){};
	f.prototype = p;
	f.prototype.constructor = f;
	return new f();
}

2、constructor是什么?
1)每个对象创建时会自动拥有一个构造函数属性constructor
2)constructor继承自原型对象,指向构造函数的引用,即它的值就是构造函数
3)构造函数中的方法会存在于每个生成的实例中,重复挂载可能导致资源浪费
3、原型对象

  function Course() {}
  const course1 = new Course();
  const course2 = new Course();

1)构造函数:用来初始化创建对象的函数-Course
:自动给构造函数赋予一个属性prototype,该属性实际等于实例对象的原型对象
2)实例对象:course1就是根据原型创建出来的实例对象
:每个实例对象中都有__proto__属性,即原型对象,
:每个实例对象都有constructor属性
:constructor由继承而来(即原型对象),并指向当前构造函数
3)原型对象:Course.prototye,原型对象包括constructor和_proto_

  // 对上篇原型对象做优化
  function Course(name) {
    this.teacher = '云隐';
    this.leader = '黄小杨';
    console.log(this.startCourse(name))
  }
  Course.prototype.startCourse = function(name) {
    return `开始${name}`;
  }
  const course1 = new Course('es6');
  const course2 = new Course('OOP');
  

实例对象可以直接访问原型链上的方法和属性,但是不能修改和删除,因为删除先删除自己的属性,修改先修改或者添加自己的属性,如果要修改原型对象必须是实例对象.proto.a这样去添加修改。
以上可以看到实例化的对象的原型对象__proto__指向构造函数的prototype属性,prototype属性里又包含__proto__和constructor,里面的__proto__指向Object.prototype属性,Object.prototype指向null。由图像显示
在这里插入图片描述

四、继承
1、继承方式
1)原型链继承----在原型对象的所有方法和属性都能被实例共享

 // Game类
  function Game() {
    this.name = 'lol';
  }
  Game.prototype.getName = function() {
    return this.name;
  }
  //LOL类
  function LOL(){
  }
  //继承
  LOL.prorotype = new Game();
  LOL.prorotype.constructor = LOL;
  const game = new LOL();
  //本质是重写原型对象,将父对象的属性方法,作为子对象的原型对象方法属性

原型链继承缺点

function Game() {
  this.name = 'lol';
  this.skin = ['s'];
}
Game.prototype.getName = function() {
  return this.name;
}

// LOL类
function LOL() {}
// LOL继承Game类
LOL.prototype = new Game();
LOL.prototype.constructor = LOL;
const game1 = new LOL();
const game2 = new LOL();
game1.skin.push('ss');//子类1修改会改变父类引用属性

缺点:1、父类属性一旦赋值给给子类的原型属性,此时属性就会被所有子类共享,其中一个通过原型对象改变父类属性,另一个子类也会受到影响
2、实例化子类时,不能向父类传参。
2)构造函数继承----在子类构造函数内部调用父类构造函数

function Game(arg) {
  this.name = 'lol';
  this.skin = ['s'];
}
Game.prototype.getName = function() {
  return this.name;
}
//lol类
function LOL(arg){
	Game.call(this,arg)
}
// LOL继承Game类
const game3 = new LOL();//这里的父类的属性都是子类自己的,实例化后不影响

它解决了共享性问题和传参问题,但是父类原型链上方法和属性无法读取访问。----组合继承
3)组合继承----构造函数和原型链一起继承

function Game(arg) {
  this.name = 'lol';
  this.skin = ['s'];
}
Game.prototype.getName = function() {
  return this.name;
}

// LOL类
function LOL(arg) {
  Game.call(this, arg);
}
// LOL继承Game类
LOL.prototype = new Game();
LOL.prototype.constructor = LOL;

const game3 = new LOL();

组合继承缺点就是实例化子类对象时会调用两次父类构造函数
一次是初始化子类原型对象时,一次是子类构造函数执行时
4)寄生组合继承----子类原型对象指向一个新的对象,新的对象的原型对象指向福类的原型对象

function Game(arg) {
  this.name = 'lol';
  this.skin = ['s'];
}
Game.prototype.getName = function() {
  return this.name;
}

// LOL类
function LOL(arg) {
  Game.call(this, arg);
}
LOL.prototype = Object.create(Game.prototype);//生成一个新对象,这个对象的原型对象__proto__指向Game.prototype
//这里也可以直接LOL.prototype = Game.prototype,但是这样做的话修改LOL.prototype属性会直接影响Game.prototype,也不好
LOL.prototype.constructor = LOL;

2、如何实现多重继承----混合父类

unction Game(arg) {
  this.name = 'lol';
  this.skin = ['s'];
}
Game.prototype.getName = function() {
  return this.name;
}

function Store() {
  this.shop = 'steam';
}
Store.prototype.getPlatform = function() {
  return this.shop;
}
function LOL(arg){
	Game.call(this,arg);
	Store.call(this,arg);
}
LOL.prototype = Object.create(Game.prototype);
Object.assign(LOL.prototype,Store.prototype);//混合父类原型对象
LOL.prototype.constructor = LOL;
const game3 = new LOL();

3、class继承和组合寄生继承有什么区别?
class继承会继承静态属性,class子类中必须在constructor里使用super,因为子类自己的this对象,必须通过父类的构造函数完成。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值