[ES6]Day07—构造函数与原型、继承

ECMAScript 6 入门 (Day07)

接上篇:[ES6]Day06—ES6中的类与对象
在这里插入图片描述

7.1.1 概述

在典型的面向对象语言中(java),都存在类的概念,类是对象的模板,对象是类的实例,但在ES6之前,js中并没有引用类的概念
在这里插入图片描述
创建对象可以通过以下三种方式:

  1. 对象字面量
   var obj={
    
    };
  1. new Object()
   var obj=new Object();
  1. 自定义构造函数

把对象的公共部分抽出放在构造函数里面,通过构造函数创建对象

function Star(uname,age){
    
    
	this.uname=uname;
	this.age=age;
	this.sing=function(){
    
    
		console.log('I can sing')
	}
}
var hh = new Star('易烊千玺',18); //创建 hh 对象
var cc = new Star('王一博',21); // 创建 cc 对象
hh.sing();
cc.sing();

7.1.2 构造函数

构造函数:一种特殊的函数,主要用来初始化对象,即为对象成员赋初始值,总与new 一起使用。把对象中的公共属性和方法抽取出来,然后封装到这个函数里面。

在这里插入图片描述
在这里插入图片描述

7.1.2 静态成员、实例成员

静态成员:由构造函数本身添加的成员,只能由构造函数本身来访问
实例成员:在构造函数内部通过this添加的成员,只能由实例化对象来访问

function Star(uname,age){
    
    
	this.uname=uname;
	this.age=age;
	this.sing=function(){
    
    
		console.log('I can sing')
	}
}
var hh=new  Star('易烊千玺',18); //创建 hh 对象 
hh.sing();
console.log(Star.uname);//undefined,不能通过构造函数Star访问
console.log(hh.uname);//易烊千玺,uname为实例成员,只能由实例化对象来访问

Star.sex='男' //静态成员
console.log(Star.sex) // ,sex为静态成员,只能由构造函数Star本身来访问
console.log(hh.sex) // undefined

以上代码的实例成员有:uname,age,sing,静态成员:sex

7.1.3 构造函数原型 prototype

构造函数存在问题: 每实例化一次对象,就会在内存中创建一个内存空间,存在浪费内存的问题。

在这里插入图片描述

console.log(ldh.sing===zxy.sing)//false,内存地址不一样,方法代码一样,还在内存中各自占用一个内存空间,存在浪费

怎么实现所有对象使用同一个函数,从而节省内存空间呢?

  • 构造函数通过原型prototype分配的函数是所有对象所共享的。

  • JS中规定,每一个构造函数都有一个prototype属性,指向一个对象,prototype就是一个对象,这个对象的所有属性与方法,都会被构造函数所拥有。

在这里插入图片描述
可以把不变的方法,直接定义在prototype对象上。这样所有对象的实例就可以共享这些方法了。

function Star(uname,age){
    
    
	this.uname=uname;
	this.age=age;
	//this.sing=function(){
    
    
	//	console.log('I can sing')
	//}
}
Star.prototype.sing =function(){
    
    
 	console.log('I can sing')
} 
var hh=new Star('易烊千玺',18); //创建 hh 对象 
var cc=new Star('王一博',21); // 创建 cc 对象
hh.sing();
cc.sing();
 

在这里插入图片描述
问题1: 原型是什么?

答:原型是每一个构造函数里都有的一个对象,所以prototype也称为原型对象

问题2:原型的作用是什么?

答:使用原型分配函数可以使,所有对象的实例能够共享方法,节省内存空间

总结:

构造函数中的公共属性定义到构造函数里面,而公共方法则放到构造函数的原型对象prototype身上

7.1.4 对象原型__proto__

function Star(uname,age){
    
    
	this.uname=uname;
	this.age=age; 
}
Star.prototype.sing =function(){
    
    
 	console.log('I can sing')
} 
var hh=new Star('易烊千玺',18); //对象身上有一个原型__proto__,指向构造函数Star的原型对象prototype
hh.sing(); 

为什么实例对象 hh、cc本身没有sing()方法,也能调用Star的原型对象中的sing()方法呢?

在这里插入图片描述

console.log(hh.__proto__===Star.prototype) //true,对象原型__proto__与构造函数Star的原型对象prototype是等价的

总结对象都会有一个属性__proto__指向构造函数的原型对象prototype,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有(两个下划线)__proto__原型的存在。

hh.sing(); 

方法查找规则:先查找hh对象身上有没有,有就执行对象hh身上的sing(),
如果对象hh本身没有,去__proto__指向的构造函数原型对象prototype身上找sing()方法。

在这里插入图片描述

7.1.5 原型中的 constructor 构造函数

对象原型__proto__与构造函数(prototype)原型对象里都有一个属性constructor ,我们称之为 构造函数,因为它指向构造函数本身

在这里插入图片描述
constructor 作用:记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数(Star)。

在这里插入图片描述
注意:

下面第二种赋值,直接覆盖了Star.prototype对象,所以其constructor 指向的是一个Object,如果我们修改了原来的原型对象,给原型对象赋值一个Object,则需要手动利用constructor 指向原来的构造函数

  1. 第一种,在构造函数原型对象中添加sing方法,constructor 指向的是构造函数Star
Star.prototype.sing =function(){
    
    
 	console.log('I can sing')
} 
  1. 第二种,直接给原型对象赋值一个Object,需要手动利用constructor 指向原来的构造函数Star。

Star.prototype={
    
     
	constructor:Star,//没有这一步,constructor指向的是一个Object
	
	sing:function(){
    
    
 		console.log('I can sing')
	}, 
	eat:function(){
    
    
 		console.log('I can eat')
	}
}  

7.1.6 构造函数、实例、原型对象三者之间的关系

在这里插入图片描述

1、Star构造函数 与 原型对象 prototype 关系
  • 每一个构造函数里面都有一个原型对象prototype,通过Star.prototype指向原型对象的;
  • 原型对象prototype里有一个属性叫constructorStar.prototype.constructor指回向构造函数
2、Star构造函数 与 实例对象 关系
  • 通过new构造函数来创建 实例对象
3、实例对象 与 原型对象 prototype 关系
  • 只要是对象就有__proto__原型,指向构造函数的原型对象prototype,它是对象独有的。
  • 实例对象的__proto__原型中有一个属性constructor,通过原型对象Star.prototype指向Star 构造函数

7.1.7 原型链(重点)

function Star(uname,age){
    
    
	this.uname=uname;
	this.age=age; 
}
Star.prototype.sing =function(){
    
    
 	console.log('I can sing')
} 
var hh=new Star('易烊千玺',18); //对象身上有一个原型__proto__,指向构造函数Star的原型对象prototype

hh.sing()

console.log(Star.prototype.__proto__===Object.prototype);//true
console.log(Object.prototype.__proto__===null);//true

1、只要是对象就有__proto__原型,指向原型对象

2、构造函数Star的原型对象prototype里的__proto__原型(Star.prototype.__proto__)指向Object原型对象(Object.prototype

3、Object 构造函数创建原型对象(Object.prototype),Object原型对象.constructor(Object.prototype.constructor)指向 Object构造函数

4、Object.prototype原型对象里的__proto__原型指向null

在这里插入图片描述
我们发现每个对象都有__proto__原型,__proto__原型又指向原型对象prototype,形成了一条原型链,当查找成员变量时,会一层一层往上找,如果查找完全部都找不到,就会显示null

总结:JavaScript 查找成员机制(规则)
在这里插入图片描述
问题1: 原型是什么?

答:原型是每一个构造函数里都有的一个对象,所以prototype也称为原型对象

问题2: 原型的作用是什么?

答:使用原型分配函数可以使,所有对象的实例能够共享方法,节省内存空间。

问题3: 什么是原型链?

  • 每个对象都可以有一个原型_proto_,这个原型还可以有它自己的原型,以此类推,形成一个原型链。
  • 查找特定属性的时候,我们先去这个对象里去找,如果没有的话就去它的原型对象里面去,如果还是没有的话再去向原型对象的原型对象里去寻找… 这个操作被委托在整个原型链上,这个就是我们说的原型链了。
    在这里插入图片描述

7.1.8 原型对象this指向

function Star(uname,age){
    
    
	this.uname=uname;
	this.age=age; 
}
var that;
Star.prototype.sing =function(){
    
    
 	console.log('I can sing')
 	that=this;
} 
var hh=new Star('易烊千玺',18);   
hh.sing();
console.log(that===hh) //true

1、在构造函数中,里面this指向的是实例对象 hh

2、原型对象函数里面的this指向的是函数调用者,这里是 实例对象 hh

7.1.9 扩展内置对象

可以通过原型对象,对原来的内置对象进行拓展自定义的方法,比如给数组增加自定义求偶数和的功能

Array.prototype.sum=function(){
    
    
	var sum=0;
	for(let i = 0; i < this.length ; i++){
    
    
		sum+=this[i];
	}
	return sum;
}

var arr=new Array(1,2,3)
arr.sum() //6

//打印 Array.prototype 

不要通过下面这种方法添加方法,这样会覆盖掉原型原有的方法,constructor指向也不一样了

// Array.prototype={
    
    
//	sum:function(){
    
    
//		var sum=0;
//		for(let i = 0; i < this.length ; i++){
    
    
//			sum+=this[i];
//		}
//		return sum;
//	}
//}

7.1.10 组合继承

ES6 之前没有提供 extends 继承。我们可以通过构造函数+原型对象模拟实现继承,被称之为组合继承

1、call()

语法:func.call(thisArg, arg1, arg2, ...argN)

参数 含义
thisArg this 的指向对象
arg1, arg2, ...argN 为调用func函数需要传入的参数,全都用逗号分隔,直接放到后面

作用:调用 func 函数,并改变函数运行时的this指向

例子:

function fn(x,y){
    
    
	console.log(this);
	console.log(x+y);
}

var o={
    
    
	name:'sam'
}
fn.call(o,3,8) 

在这里插入图片描述

2、apply()

语法:func.apply(thisArg, [argsArray])

参数 含义
thisArg 必选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
[argsArray] 可选的。但是必须是一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。

作用:apply() 方法调用一个参数为数组(或类似数组对象),给定this值的函数。

例子:

function fn(x,y){
    
    
	console.log(this);
	console.log(x+y);
} 
var o={
    
    
	name:'sam'
}
fn.apply(o,[3,8])  //输出 {
    
    name:'sam'}  11 
fn.apply(o,3,8)    //报错 ,因为第二个参数不是数组或类数组

在这里插入图片描述

var arr=[1,54,78,3,6,85,100]
Math.max.apply(Math,arr); //不改变this指向,求数组最大值
//100
3、bind()

语法:let boundFunc = func.bind(thisArg, arg1, arg2, ...argN)

参数 含义
thisArg this 的指向对象
arg1, arg2, ...argN 调用func函数需要传入的参数,全都用逗号分隔,直接放到后面,返回一个函数(闭包),函数延迟执行,需要调用才执行
return 返回由指定的this值和初始化参数改造后的原函数拷贝,相当于返回一个新的函数

作用:bind()不会调用函数,但是能改变函数内部的this指向。 如果有的函数不需要立即调用,但是又想改变这个函数内部的this指向此时用bind()

例子:

function fn(x,y){
    
    
	console.log(this);
	console.log(x+y);
}

var o={
    
    
	name:'sam'
}
fn.bind(o,3,8)  // 输出fn 函数,不会执行
fn.bind(o,3,8)() //执行 fn 函数, 输出 {
    
    name:'sam'}  11 

在这里插入图片描述

实例: 有一个按钮,当我们点击之后,禁用按钮,3秒钟后开启按钮

var btn =document.querySelector('button');
btn.onclick=function(){
    
    
	this.disabled=true; //这个this 指向btn 按钮
	console.log(this);//定时器函数里面的 this 指向 btn
	
	//setTimeout(function(){
    
    
	//	console.log(this);//定时器函数里面的 this 指向window 
	//},3000)

	setTimeout(function(){
    
    
	    console.log(this);//加了bind(),定时器函数里面的 this 指向 btn
	 }.bind(this),3000) //this 在定时器外面,指向的是btn对象 ,将btn对象绑定到定时器上
	}
}

多个按钮
在这里插入图片描述
在这里插入图片描述
改变定时器中的this指向,原来是指向Window,改变之后指向btn ,而且不会立即调用函数

4、call() 、bind() 、 apply() 区别:
var obj={
    
    
    name:'张三',
    age:18,
    myFun:function(fm,to){
    
    
        console.log(this.name+" 年龄"+this.age,fm + "去往" + to);
    }  
}
var db={
    
    
    name:'德玛',
    age:99
}

obj.myFun.call(db,'成都','上海');   //德玛 年龄99 成都去往上海

obj.myFun.apply(db,['成都','上海']);   //德玛 年龄99 成都去往上海  

obj.myFun.bind(db,'成都','上海'); // 返回一个函数,这里就是 bind  call/apply 的区别之一,bind 的时候不会立即执行

obj.myFun.bind(db,'成都','上海')();    //德玛 年龄99 成都去往上海

obj.myFun.bind(db,['成都','上海'])();  //德玛 年龄99 成都,上海去往undefined

相同点

1、都可以改变上下文 this 指向的,第一个参数都是 this 的指向对象
2、三者的参数不限定是string类型,允许是各种类型,包括函数 、 object

不同点

1、call()apply() 会调用函数,并且改变函数内部this指向。

2、传递参数形式不同call() 后面的参数全都用逗号分隔 ,apply() 后面的参数必须传一个数组

 obj.myFun.call(db,'成都', ... ,'string' );
 obj.myFun.apply(db,['成都', ..., 'string' ]);

3、bind()不会调用函数,除了返回的是原函数改变this之外产生的新函数以外,它的参数和call()一样。如果有的函数不需要立即调用,但是又想改变这个函数内部的this指向此时用bind()方法
    
4、应用对象不同call()apply() 是直接使用在函数上,而 bind() 绑定 this 后返回执行上下文被改变的函数(闭包),不会立即执行,需要()才会执行。

5、主要应用场景不同call()常用在继承;apply() 经常与数组有关,比如借助于数学对象实现数组最大值最小值;bind()不调用函数,但是还想改变this指向。比如改变定时器内部的this指向。

Array.prototype.sum=function(){
    
    
	var sum=0;
	for(let i = 0; i < this.length ; i++){
    
    
		sum+=this[i];
	}
	return sum;
}

var arr=new Array(1,2,3)
arr.sum() //6

//打印 Array.prototype 
5、借用构造函数继承父类型属性

核心原理:通过call()把父类型的this指向子类型的this,这样就实现子类型继承父类型的属性。

function Father(uname,age){
    
    
	//this指向父构造函数 的对象实例
	this.name=uname;
	this.age=age;
}

function Son(uname,age,score){
    
    
	//this指向 子构造函数 的对象实例
	Father.call(this,uname,age) //改变this指向 
	this.score=score; //儿子有自己的属性
}
var son =new Son('易烊千玺',25)
console.log(son) 
6、借用原型对象继承方法
function Father(uname,age){
    
    
	//this指向父构造函数 的对象实例
	this.name=uname;
	this.age=age;
}
Father.prototype.money=function(){
    
    
	console.log("1000元") 
}
 
function Son(uname,age,score){
    
    
	//this指向 子构造函数 的对象实例
	Father.call(this,uname,age) //改变this指向 
	this.score=score; //儿子有自己的属性
}

// Son.prototype=Father.prototype //不能这样直接赋值,如果修改了子原型对象(添加exam 方法),父原型对象也会改变 
 
Son.prototype=new Father();  //相当于= 新的对象 {
    
    }
// 如果利用对象形式修改 原型对象,别忘了利用constructor 指回 原来的构造函数
Son.prototype.constructor = Son; //如果不改回来,Son.prototype.constructor 指向的就是Father构造函数了。

Son.prototype.exam=function(){
    
    
	console.log("只有儿砸才要考试哦!");
}
var son =new Son('易烊千玺',18,100)
console.log(son) 
console.log(Father.prototype)

第一种:Son.prototype=Father.prototype

这样直接赋值会有问题,如果修改了子原型对象(添加exam 方法),父原型对象也会改变(父亲也添加了exam),所以不能这样直接赋值
在这里插入图片描述

第二种:

 Son.prototype=new Father() 
 Son.prototype.constructor = Son;     

在这里插入图片描述

1、通过创建一个父实例对象,让子原型对象指向该父实例对象,这样即实现了继承父构造函数的money方法,同时修改子原型对象(添加exam 方法)也不会直接修改父原型对象,也能创建属于子构造函数的方法

2、如果利用对象形式修改 原型对象,利用 constructor 指回 原来的构造函数(Son构造函数),如果不改回来,就会出现如下:
在这里插入图片描述
在这里插入图片描述

参考链接:

阮一峰:ECMAScript 6 入门

猜你喜欢

转载自blog.csdn.net/iChangebaobao/article/details/106823065