通过阅读本文你会了解到什么是:原型和原型链、普通对象和函数对象、
__proto__
和prototype
、构造函数(constructor) 。
1、普通对象和函数对象
在javascript中万物皆对象,对象分为普通对象和函数对象,可以通过typeof
来判断对象是哪一种类型的对象。
1.1 普通对象
typeof obj === 'object'; // true
let obj1 = {};
let obj2 = new Object();
let obj3 = new Array();
console.log(typeof obj1); // object
console.log(typeof obj2); // object
console.log(typeof obj3); // object
1.2 函数对象
typeof obj === 'function'; // true
let f1 = new Function('a', 'b', 'return a + b');
let f2 = function () {};
function f3() {};
console.log(typeof f1); // function
console.log(typeof f2); // function
console.log(typeof f3); // function
console.log(typeof Function); // function
console.log(typeof Object); // function
console.log(typeof Array); // function
console.log(Function.constructor === Function); // true
console.log(Object.constructor === Function); // true
console.log(Array.constructor === Function); // true
注: 凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。f1,f2,f3,实际上也是要通过 new Function()的方式进行创建。Function Object 也是通过 New Function()创建的。javascript大部分内置对象的构造器属于函数对象。
2、构造函数
构造函数是用来初始化新创建的对象的,属于函数对象。
(1)使用构造函数创建两个实例
function Student(name, age) {
this.name = name;
this.age = age;
}
let student1 = new Student('Ada', 23);
let student2 = new Student('Bob', 25);
console.log(student1.name, student1.age); // Ada 23
console.log(student2.name, student2.age); // Bob 25
console.log(typeof Student); // function
如果使用构造函数来初始化新创建的对象,记得用new
关键字。
(2)错误示范:如果不用new
关键字,那么this
就会指向当前调用该构造函数的对象,本例中是window
let student3 = Student('Sunny', 23);
console.log(student3); // undefined
console.log(window.name, window.age); // Sunny 23
(3)构造函数创建的实例和构造函数间的关系
console.log(student1.constructor === Student); // true
console.log(student2.constructor === Student); // true
注: 用构造函数创建的实例都具有constructor
属性,该属性指向其构造函数。实例.constructor === 构造函数
。
3、原型(原型对象)- prototype
- 原型是一个对象(也被称为原型对象),是类的核心,可以通过原型实现原型(对象)的属性继承。
- 某些对象有些预定义的属性,其中函数对象有一个
prototype
对象,指向其原型对象。
(1)创建两个Student实例
function Student(name, age) {
this.name = name;
this.age = age;
}
Student.prototype.sayHello = function () {
console.log(`hello ${this.School}, my name is ${this.name}.`);
};
Student.prototype.School = 'CUIT';
let student1 = new Student('Ada', 23);
let student2 = new Student('Bob', 25);
student1.sayHello(); // hello CUIT, my name is Ada.
student2.sayHello(); // hello CUIT, my name is Bob.
console.log(student1.prototype); // undefined
通过对构造函数的原型对象的属性赋值,可以让所有的实例都具有构造函数.prototype
中的所有属性。其属性值中的this
指代当前实例对象(最好不要用箭头函数来书写函数,不然this的指代不同)。
所以: prototype
属性只有函数对象才有。
(2)Student.prototype
console.log(Student.prototype.constructor === Student); // true
原型对象具有constructor
属性,指向prototype
属性所在的函数(构造函数)。上文我们有推断实例.constructor === 构造函数
,所以我们可以仅仅认为(Student.prototype instanceof Student ; // false
)构造函数.prototype是构造函数的一个实例。
(3)特殊的Function.prototype
一般情况下我们可以认为原型对象是一个普通对象,但是Function.prototype
是一个函数对象。我们上文提到过凡是通过 new Function() 创建的对象都是函数对象,且我们可以认为构造函数.prototype是构造函数的一个实例
,所以Function.prototype就是一个函数对象。
4、_proto_
javascript在创建对象时候,都有一个叫做__proto__
的内置属性,用于指向创建它的构造函数的原型对象。但是__proto__
并不是一个规范的属性,只有部分浏览器(比如:Firefox和Chrome)才支持。
console.log(student1.__proto__ === Student.prototype); // true
实例.__proto__ === 构造函数t.prototype; // true
(1)实例的_proto_和构造函数及其原型间的关系
function testProto(factory) {
let obj = new factory();
console.log(obj.constructor === factory);
console.log(obj.__proto__ === factory.prototype);
}
testProto(Array); // true true
testProto(Date); // true true
testProto(Function); // true true
(2)javascrpt部分内置构造器和Function间的关系
console.log(Date.constructor === Function); // true
console.log(Date instanceof Function); // true
console.log(Date.__proto__ === Function.prototype); // true
console.log(Object.constructor === Function); // true
console.log(Object instanceof Function); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(Function.constructor === Function); // true
console.log(Function instanceof Function); // true
console.log(Function.__proto__ === Function.prototype); // true
所有函数对象的proto都指向Function.prototype,它是一个空函数(Empty function),但是其原型对象有一些内置的属性:如length、call、apply、bind等。
(3)Function.prototype.__proto__
console.log(Function.prototype.__proto__ === Object.prototype); // true
这说明所有的构造器也都是一个普通对象,所以可以给构造器添加/删除属性等。同时它也继承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等。
(4)Object.prototype.__proto__
console.log(null === Object.prototype.__proto__ ); // true
5、原型链
原型链,由原型对象组成的链式关系。因为每个对象都有原型,原型也是对象,也具有原型,这样一层一层往上链接,最后就构成了原型链。原型链最顶端(Object.prototype.__proto__
)指向null。原型链的形成真正是靠_proto_ 而非prototype
6、总结
函数对象才有prototype
(实例共享属性和方法)属性,对象都有__proto__
(指向该对象构造函数的原型)属性。
构造函数.prototype == 构造函数.prototype
构造函数.prototype.constructor == 构造函数
函数对象.__proto__ == Function.prototype
构造函数.__proto__ == Function.prototype
实例.__proto__ == 构造函数.prototype
实例.constructor == 构造函数
typeof Function.prototype // function
Function.__proto__ == Function.prototype
Function.constructor == Function
Function.prototype.__proto__ == Object.prototype
-
Object.prototype.__proto__ == null
6、小练习
(1) new Foo().__proto__
?
(2) Foo.__proto__
?
(3) Foo.prototype.__proto__
?
(4) Object.__proto__
?
7、友情链接
注:本文内容是看了Yi罐可乐-最详尽的 JS 原型与原型链终极详解3系列总结出来的。