一、构造函数
构造函数和普通函数没什么区别,只是用了new关键字创建对象。(es6中可以使用class, class可以看作只是一个语法糖,它的绝大部分的功能,ES5都可以做到,新的class写法只是让原型的写法更加的清晰、更像面向对象编程的语法而已。)
function Person(name, age) {
this.name = name;
this.age = age;
}
let person1 = new Person('tom', 20);
如上,js通过构造函数来生成实例,但是出现一个问题,在构造函数中通过this赋值的属性或者方法是每个实例的实例属性和实例方法,无法共享公共属性,所以设计出一个原型对象来储存这个构造函数的公共属性和方法。 补充:构造函数创建一个实例的过程如下
- 创建一个新对象
- 将构造函数的作用域赋值给新对象(this指向新对象)
- 执行构造函数中的代码(为新对象添加实例属性和实例方法)
- 返回新对象
二、原型对象
js的函数在创建的时候会生成一个prototype属性,这个属性指向一个对象,这个对象就是此函数的原型对象,该原型对象有个属性constructor,指向该函数。
原型对象的作用:主要用来存放实例对象的公有属性和公有方法,因为放在构造函数里,每创建一个实例,就会重复的创建一次相同的属性和方法。
1、显示原型: prototype,这个是函数类型数据的属性
2、隐式原型: _ _ proto _ _,这个属性指向构造函数的原型对象,这个属性是对象类型数据的属性。
注意:_ _ proto _ _ 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JS 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。(阮一峰的ES6入门)
三、原型链
// 构造函数
function Preson(name, age) {
this.name = name;
this.age = age;
}
// 所有实例共享的公共方法
Preson.prototype.say = function (word) {
console.log(`${this.name}说:${word}`);
}
let p1 = new Person('tom', 20); // 创建一个Person实例对象
p1.hasOwnProperty('say') // false 说明不是定义在其本身上的
p1.say('hello world'); // 调用公共方法 打印:tom说:hello world
构造出p1这个实例对象为什么可以调用到Person构造函数原型对象上的方法,只有在构造函数内部通过this来赋值的属性或方法才会被实例继承,为什么构造函数原型对象上的say也可以通过实例调用,这就是原型链的作用。
当访问一个对象的属性会先在对象本身属性上查找,没有找到会通过_ _ proto _ _ 隐式属性找到构造函数的原型对象,如果还没找到会在其构造函数的原型对象的_ _ proto _ _ 中查找,这样形成的链式结构就是原型链,原型链也叫做隐式原型链。
分析一下,原型对象也是对象,构造函数原型对象的_ _ proto _ _ 指向哪儿?我们只要找到对象构造函数就能找到指向了,在js中,对象的构造函数就是Object(),所以对象的原型对象就是Object.prototype。Object.prototype比较特殊,它没有上一层的原型对象,也就是它的_ _ proto _ _ 指向null。因此关系图为:
注意:不要通过实例对象去改变构造函数的原型对象,这样其他通过该构造函数生成的实例对象也会被影响。
在js中,函数也是一种对象,那函数也有_ _ proto _ _ 属性,而所有函数可以看作是Function()的实例,而Person()和Object()都是函数,所以他们的构造函数就是Function(),Function()本身也是函数,所以它也有自己的实例。那么函数的_ _ proto _ _ 就是Function.prototype。
console.log(Person.constructor === Function); // true
console.log(Object.constructor === Function); // true
console.log(Function.constructor === Function); // true
console.log(Person.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true
所以最终的关系图如下:
四、加深理解
function Person() {}
var p = new Person()
console.log(Person.prototype); // Object{}
console.log(p.prototype); // undifined
console.log(p.constructor); //function Person(){}
//此处的p是通过 Person函数构造出来的,所以p的constructor向Person
console.log(Person.constructor); //function Function(){}
//上面提过,每个函数其实是通过new Function()构造的
console.log({}.constructor); // function Object(){}
//每个对象都是通过new Object()构造的
console.log(Object.constructor); // function Function() {}
//Object也是一个函数,它是Function()构造的
console.log([].constructor); //function Array(){}
再理解一下下面这个
console.log(Function instanceof Object); // true
console.log(Object instanceof Function); // true
函数是对象构造的,对象也是函数构造的,两者既是函数也是对象,所以为什么构造函数它是一个函数却返回一个对象,两者是互相继承的关系。