构造函数、原型对象、实例对象的关系?
每一个构造函数都有一个原型对象
原型对象都包含一个指向构造函数的指针
实例对象都包含一个指向原型对象的内部指针
构造函数(Constructor)是什么?
构造函数就是一个普通的函数,创建方式和普通函数没有区别,但是函数名首字母要大写,普通函数可以函数名加括号直接调用,构造函数必须通过new关键字创建实例对象来调用函数
比如:Person是构造函数
function Person() {}
函数对象和普通对象:
通过 new 创建的实例对象都是函数对象,其它的就是普通对象
比如:
//函数对象
var foo = new function() {
}
//或者
function Foo() {
}
var foo = new Foo()
**实例对象:**通过new调用构造函数实例化出来的对象
比如:Person 是构造函数,person1是实例对象
function Person() {} //声明构造函数Person
var person1 = new Person() //使用new操作符调用Person实例化了一个对象person1
prototype(原型):
prototype代表原型,原型是每一个函数对象都具有的属性,
普通对象没有prototype原型属性
比如:prototype是原型属性,Person.prototype就是原型对象,也就是构造函数的一个prototype属性,prototype属性指向原型对象Person.prototype
function Person() {}
var person1 = new Person()
原型的作用:
就是为了把所有的对象连接起来,共享公共属性和方法,通过构造函数生成的所有实例对象都能够共享属性,这个共享属性就存放在原型对象里
原型与构造函数的关系:
有构造函数,就可以在原型上创建可以 继承 的属性,也就是在原型上添加属性,这些属性被new操作符创建的实例对象所共享
proto(隐式原型属性):
每一个对象隐含的一个属性。函数对象和普通对象都有 __proto__隐式原型属性,__proto__用于指向自身构造函数的原型对象prototype
比如:
实例对象的隐式原型属性指向构造函数原型对象
function Person() {}
var person1 = new Person()
//实例对象通过隐式原型指向原型对象
person1.__proto__ === Person.prototype
//构造函数的隐私原型指向null
Person.__proto__ === null
总结:属性用来指向对象
构造函数的原型属性指向这个构造函数的原型对象
实例对象的隐式原型属性__proto__指向构造函数的原型对象
构造函数的__proto__指向Function的原型对象
Function的__proto__指向Function的原型对象
Function的原型对象的__proto__执向Object的原型对象
Object的原型对象的__proto__指向null
比如:
function Person(name) {
this.name = name;
}
let person = new Person('xiaoming')
console.log(person.__proto__ == Person.prototype) //true
console.log(Person.__proto__ == Function.prototype) //true
console.log(Function.__proto__ === Function.prototype) //true
console.log(Person.prototype.__proto__ == Object.prototype) //true
console.log(Function.prototype.__proto__ === Object.prototype) //true
console.log(Object.prototype.__proto__ === null) //true
Object作为JavaScript的内建函数由Function构造出来,Function作为内建构造函数又是对象,函数都是对象
原型链的理解:
原型链是实现继承的一种重要方式。
每个构造函数都有一个原型对象prototype,每一个实例对象都存在一个__proto__,即隐式原型属性。查找对象的属性时,首先查找对象本身是否存在该属性,不存在的话就会通过对象的隐式原型属性__proto__在原型链上查找
因为JS中万物皆对象,所有的对象都存在__proto__属性,就会形成一条__proto__链条,__proto__最终会指向原型链的顶端,即Object.prototype,Object.prototype.proto 指向null
// 定义一个构造函数 Fun
var Fun = function() {
}
// 在构造函数的原型上添加共享方法 name
Fun.prototype.name = function() {
console.log('xiaoming')
}
// new调用构造函数,生成实例化对象fn
var fn = new Fun();
// fn调用name方法
fn.name(); //输出xiaoming
为什么调用fn.name()时,会输出 ‘xiaoming’?
fn本身不存在name方法,那么就会通过__proto__去寻找name方法,fn是Fun的实例,所以 fn.__proto === Fun.prototype,在原型上添加的方法是被实例对象共享的,所以name方法 可以被实例对象fn调用
继承的方式有哪些?
(1)原型链继承
将子类的原型对象赋值给父类的实例
比如:
// 父类型
function Father(name) {
this.name = name
}
Father.prototype.age = function() {
console.log('10')
}
function Son() {
}
// 将子类的原型对象赋值给父类的实例
Son.prototype = new Father('5')
const son = new Son()
console.log(son.name) //输出 5
son.age(); // 输出 10
原型链实现继承本质就是 将子类原型对象指向父类的实例对象,所以当子类的实例通过__proto__访问子类的原型对象时,也可以访问到父类的实例对象,
(2)借用构造函数继承
在子类型构造函数通过call()调用父类型的构造函数,改变this的指向执行一次构造函数
function Father() {
this.name1 = '1'
}
function Son() {
Father.call(this)
this.name2 = '2'
}
const son = new Son()
console.log(son)
结果:
(3)组合继承(原型链 + 构造函数)
通过调用父类构造函数,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Father() {
this.numbers = ['1', '2', '3']
}
function Son() {
Father.call(this)
}
const son = new Son()
// 父类的实例作为子类的原型
son.prototype = new Father()
console.log(son.numbers)
console.log(son)
结果:
(4)寄生式继承
通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免组合继承的缺点