原型
原型对象
- 在JS中,每一个对象都会有一个原型对象,而函数也属于对象,故构造函数都有一个
prototype
属性,指向另外一个对象,该对象称为原型对象。 prototype
属性指向一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。- 可以把那些不变的方法,直接定义在
prototype
属性指向的原型对象上,这样所有的实例对象就可以共享这些方法了。 - 原型对象的作用为了共享方法。
__proto__
对象原型
- 实例对象都会有一个属性
__proto__
指向构造函数的prototype
原型对象。 __proto__
对象原型===prototype
原型对象。__proto__
对象原型的意义就在于为实例对象的查找机制提供一个方向或者一条路线。但是它是一个非标准属性,因此在开发中,不可以使用这个属性,它只是内部指向prototype
原型对象。
constructor构造函数
__proto__
对象原型或者prototype
原型对象里面有一个属性constructor
,该属性指向构造函数本身,故称之为constructor
构造函数。constructor
构造函数主要用于记录该原型对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。- 一般情况下,对象的方法都在构造函数的原型对象中设置。若有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象
constructor
就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个constructor
指向原来的构造函数。但是如果需要往内置对象添加拓展方法时,不可以使用这种方法。
三者关系
原型链
定义
JS的成员查找机制,当访问一个对象的属性和方法时,首先查找这个对象自身有没有该属性;若没有就查找它的原型对象(也就是__proto__
属性指向的对象);若还没有就查找原型对象中__proto__
属性指向的原型对象。以此类推一直找到null
为止。
__proto__
对象原型的意义就在于为对象成员查找机制提供一个方向。
原型对象this指向
构造函数中的this
指向实例对象。
原型对象里面放的是方法,这个方法里面的this
指向的是这个方法的调用者,也就是这个实例对象。
扩展内置对象
可以通过原型对象对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求偶数和的功能。
注意:数组和字符串内置对象不能给原型对象覆盖操作,不能是Array.prototype = {}
,只能是Array.prototype.xxx = function(){}
的方式。
原型链图示
Class
创建类
- 类
constructor
构造函数
constructor
方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过new
关键字创建对象实例时,会自动调用该方法。若没有显式定义,则类内部会自动给我们创建一个constructor
class Person {
constructor(name, age) { // constructor:构造器或者构造函数
this.name = name;
this.age = age;
}
// 也可以不用构造器
static name = 'andy'; // 该name是静态属性
readonly age = '18'; // 该age是只读属性
}
- 类中添加方法
方法之间不能加逗号分割,同时方法不需要添加function
关键字
class Person {
constructor() {},
say() {...} // say()是类中的方法
}
创建实例
var xxx = new ClassName()
。
类必须使用new
关键字。
类的继承
super
关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数(通过super
.函数名调用)。
class Person {
constructor(surname) {
this.surname = surname;
}
}
class Student extends Person {
constructor(surname, firstname) {
// 调用父类的constructor(surname)
super(surname); // 此处的super可以认为是Person父类的constructor
// 定义子类独有的属性
this.firstname = firstname;
}
}
-
子类在构造函数中使用
super
,必须放在this
前面(必须先调用父类的构造方法,再使用子类构造方法)。 -
在ES6中类没有变量提升,故必须先定义类,才能通过类创建实例化对象。
-
类里面的共有属性和方法一定要加
this
使用。 -
一定要注意类里面的
this
指向问题,类的constructor()
里面的this
指向实例化对象,普通方法里面的this
指向这个方法的调用者。
类的本质
-
class
的本质还是function
,类的所有方法都定义在类的prototype
属性上。类创建的实例对象,里面也有__proto__
指向类的
prototype
原型对象。故ES6的类它的绝大部分功能,ES5都可以做到,新的class
写法只是让对象原型的写法更加清晰、更加像面向对象编程的语法。 -
故ES6的类其实就是语法糖。语法糖就是一种便捷写法。简单理解,有两种方法可以实现同样的功能,但是一种写法更加清晰、方便,那么这个方法就是语法糖。
继承实现
组合继承
ES6之前并没有提供extends
继承,此时可以通过构造函数+原型对象模拟实现继承,故称为组合继承。
借用构造函数继承父类属性
核心原理:通过call()把父类的this指向子类型的this,这样就可以实现子类继承父类的属性。
// 父类
function Person(name, age, sex){
this.name = name;
this.age = age;
this.sex = sex;
}
// 子类
function Student(name, age, sex, score){
// 此时父类的this指向子类的this,同时调用这个函数
Peroson.call(this, name, age, sex);
}
借用原型对象继承父类方法
一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。
// 核心原理
// 1. 将子类所共享的方法提取出来,让子类的prototype原型对象 = new 父类()
// 2. 本质:子类原型对象等于父类的实例化对象,因为父类实例化之后另外开辟了内存空间,就不会影响原来父类的原型对象
// 3. 将子类的constructor重新指向子类的构造函数
// Student.prototype = Person.prototype; 这样赋值会有问题,若修改了子原型对象,父原型对象也会跟随一起变
Student.prototype = new Person();
// 若利用对象的形式修改了原型对象,别忘了利用constructor指回的构造函数
Student.prototype.constructor = Student;
// 这个是子构造函数专门的方法,实际上是new Person()这个地址的对象
Student.prototype.exam = function(){
console.log("学生要考试!")
}
使用组合继承实现示例
function Person(name, age){
this.name = name
this.age = age
}
function Student(name, age, score){
Person.call(this, name, age)
this.score = score
}
Student.prototype = new Person()
Student.prototype.constructor = Student
Person.prototype.say = function(){
console.log('say')
}
Student.prototype.exam = function(){
console.log('exam')
}
const student = new Student('老墨', 18, 100)
类继承
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
say() {
console.log('say')
}
}
class Student extends Person {
constructor(name, age, score) {
super(name, age)
this.score = score
}
exam() {
console.log('exam')
}
}
const student = new Student('老墨', 18, 100)
this指向改变方法
call方法
call
方法接收的第一个参数就是this
指向的目标对象,后面的参数都作为参数传入目标对象中,此方法只是临时改变this
指向一次
function fn(...args){
console.log(this,args);
}
let obj = {
myname:"张三"
}
fn.call(obj, 1, 2); // this会变成传入的obj,传入的参数是参数序列
fn(1, 2) // this指向window
apply方法
apply
方法接收的第一个参数就是this
指向的目标对象,第二个参数是函数接收的参数,但必须是数组。此方法只是临时改变this
指向一次
function fn(...args){
console.log(this,args);
}
let obj = {
myname: "张三"
}
fn.apply(obj, [1, 2]); // this会变成传入的obj,传入的参数必须是一个数组
fn(1, 2) // this指向window
bind方法
bind
方法接收的第一个参数就是this
指向的目标对象,后面传入的也是函数的参数序列(但是这个参数列表可以分多次传入)
改变this
指向后,不会执行,返回一个永久改变this
指向的函数
function fn(...args){
console.log(this,args);
}
let obj = {
myname:"张三"
}
const bindFn = fn.bind(obj); // this 也会变成传入的obj ,bind不是立即执行的,而是返回一个改变this指向后的函数
bindFn(1,2) // this指向obj
fn(1,2) // this指向window
小结
- 三者都可以改变函数的
this
指向。第一个参数都是this
指向的目标对象,若第一个参数忽略
或为undefined
或为null
,其函数的this指向都是window
- 三者都可以传参,其中
apply
必须传入一个数组作为参数,call
则传入一个参数序列,bind
则可以多次传入参数 bind
不会执行函数,而是返回一个改变this
指向的函数,返回的函数是永久性改变this
的;apply
和call
会立即执行函数,改变的this
指向只是临时的