写在前面
JS的原型以及原型链的知识一直是JS中的一个难点和重点,想要学习好JS,必定要先翻过这座大山,本篇文章是对于该知识点的一个记录,方便自己回头翻阅。
引用类型
JavaScript中的数据类型分为基本数据类型以及引用类型,另一篇博客有讲到关于这方面的知识点。
JavaScript的基本类型和对象引用总结
了解了这些基础之后,我们要知道,引用类型除了对象
之外,其实数组
、函数
都属于引用类型。他们都具有自由扩展性,也就是可以动态添加属性和方法。其实数组和函数本质上也是一种对象。这也是为什么说JS万物皆对象。
普通对象和函数对象
虽然JS万物皆对象,但是对象也是有区别的,分为普通对象
和函数对象
。这两者很容易区分:本身是函数,或者是通过new Function构造出来的对象都是函数对象,其余都算是普通对象,举个例子:
<script>
var obj1 = {};
var obj2 = new Object();
var obj3 = new f1();
function f1(){};
var f2 = function(){};
var f3 = new Function('name','console.log(name)');
console.log(typeof Object); //function
console.log(typeof Function); //function
console.log(typeof f1); //function
console.log(typeof f2); //function
console.log(typeof f3); //function
console.log(typeof obj1); //object
console.log(typeof obj2); //object
console.log(typeof obj3); //object
</script>
Object
、Function
是JS自带的函数对象。
构造函数
构造函数是用来构造对象的,也可以说是实例化对象的,类似于其他面向对象语言类的概念,不过并不是类(ES6中已经有类的概念了),看名字就知道了,构造函数就是一个函数,看例子:
<script>
function Person(name,age){
this.name = name;
this.age = age;
}
var person1 = new Person("张三",21);
var person2 = new Person("张三",22);
console.log(person1.constructor === person2.constructor);
</script>
Output:true
上面的例子有两个实例对象,person1和person2都是通过Person这个构造函数实例化出来的,每个实例对象都有一个属性叫constructor
,这个属性是一个指针,指向的是该实例对象的构造函数。所以person1和person2的constructor相等,因为指向了同一个构造函数。
原型规则
1.之前我们提到过,对象、数组、函数都属于引用类型,所有的引用类型都有一个__proto__
属性,称之为隐式原型
,属性值是一个普通对象。
2.所有的函数对象都有一个prototype
属性,称之为显式原型
,属性值也是一个普通对象。
3.所有引用类型的__proto__
属性值都指向它构造函数的prototype
属性值
<script>
function Person(name){
this.name = name;
}
var lan = new Person("Lan");
console.log(lan.__proto__ === Person.prototype);
</script>
Output: true
根据输出便可证明上面我们所说的第三条规则。
总结这三条规则,我们可以得出一条结论
每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性
原型对象
原型对象顾名思义,就是一个普通对象,其实就是Person.prototype
,每个原型对象都会有一个默认的属性就是constructor
,这个属性(是一个指针)指向 prototype 属性所在的函数(Person),我们可以在原型对象上面增加属性值和方法,这样通过构造函数实例化的对象都能拥有相同的属性和方法了。
<script>
function Person(){
Person.prototype.name = "Lan";
Person.prototype.sayName = function(){
console.log(this.name);
}
}
var person1 = new Person();
var person2 = new Person();
console.log(person1.name, person2.name);
console.log(person1.sayName === person2.sayName);
</script>
Output:
Lan Lan
true
那么如果我们试图去寻找一个对象本身没有属性的时候,那么就会去它的__proto__
,也就是它构造函数的prototype
中去寻找。
<script>
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.sayName = function(){
console.log(this.name);
}
var person1 = new Person("Lan", 22);
person1.sayAge = function(){
console.log(this.age);
}
person1.sayAge();
person1.sayName();
</script>
Output:
22
Lan
从上面的代码可以知道,person1本身的属性只有name、age和一个sayAge方法,并没有sayName方法。所以当我们调用person1.sayName()方法时,会调用person1.__proto__.sayName()
。值得注意一点的是:无论属性是本身或者还是从原型上面找的,this永远都是指向person1本身
原型链
<script>
function Person(name, age){
this.name = name;
this.age = age;
}
var person1 = new Person("Lan", 22);
person1.toString();
</script>
person1.toString()
,当我们的对象没有这个属性的时候,就会去它自身的隐式原型__proto__
中去寻找,也就是去它构造函数的显式原型prototype
中去寻找,但是Person.prototype中也没有toString(),因为显式原型本身也是一个对象,也有自己的隐式原型__proto__
,于是它又会去它的隐式原型中去找,即在其构造函数 (Object )的显式原型中去找 toString,再往上找就是null了。下面是原型链的示意图:
instanceof
instanceof
可以用于判断一个引用类型是否属于某个构造函数:
<script>
function Person(){}
var person1 = new Person();
console.log(person1 instanceof Person);
</script>
Output: true
其实instanof
的原理就是person1一层一层往上找,看能否对应到 Person.prototype(显式原型)。
利用原型链继承
我们知道了原型链的原理,我们便可以利用这个原理去实现继承。
<script>
function Animal(){
this.eat = function(){
console.log("animal eat")
}
}
function Dog(){
this.bark = function(){
console.log("dog bark");
}
}
Dog.prototype = new Animal();
var dog = new Dog();
dog.eat();
</script>
我们可以手动的把一个Animal的实例赋值给Dog的prototype。这样就可以沿着原型链找到Animal中的属性。也就实现了继承的效果。