原型的基础知识点
一个简单的对象,用于实现对象的属性继承。可以简单的理解成对象的父类。在浏览器中,每个 JavaScript 对象中都包含一个 __proto__ (非标准)的属性指向该对象的原型,可 obj.__proto__进行访问。只有函数包含prototupe。
五条原则需要牢记
- 所有引用类型(函数、对象、数组),都存在对象特性,即可以自由拓展属性。(除了null意外)
- 所有的引用类型(函数、对象、数组),都有一个_proto_(我们这里称他为隐形原型)属性,属性值是一个普通的对象。
- 所有函数都有一个prototype属性,属性值也是一个普通的函数
- 所有的引用类型(函数、对象、数组),_proto_属性值指向它的构造函数的 prototype(显性属性)属性值。
- 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的_proto_(即他的构造函数的prototype)中寻找。如果没有,则会接着往上找,一直上溯到Object.prototype,也就是说所有对象都继承Object.prototype的属性,Object.prototype的原型是null,null没有任何属性和方法。
对象分为普通对象和函数对象,凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。
例如: f1、f2、f3、Object、Function都是函数对象,而o1、o2、o3都是普通对象
function f1(){};
var f2 = function(){};
var f3 = new Function('str','console.log(str)');
var o1 = {};
var o2 =new Object();
var o3 = new f1();
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 o1); //object
console.log(typeof o2); //object
console.log(typeof o3); //object
构造函数创建对象:
function Person() {
}
var person = new Person();
person.name = 'Kevin';
console.log(person.name) // Kevin
Person 就是一个构造函数(函数对象),我们使用 new 创建了一个实例对象 person(普通对象)
prototype
每个函数对象都有一个prototype
属性,这个属性指向函数的原型对象。
每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。
function Person() {
}
// 虽然写在注释里,但是你要注意:
// prototype是函数才会有的属性
Person.prototype.name = 'Kevin';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Kevin
console.log(person2.name) // Kevin
proto
每一个JavaScript对象(除了 null )都具有的一个属性,叫proto,这个属性会指向该对象的原型。每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性
function Person() {
}
var person = new Person();
Person.prototype.constructor == Person;
person1.__proto__ == Person.prototype;
person1.constructor == Person;
constructor
在默认情况下,所有的原型对象(Person.prototype)都会自动获得一个 constructor
(构造函数)属性,这个属性(是一个指针)指向 prototype
属性所在的函数(Person)。
实例的构造函数属性(constructor)指向构造函数 :person1.constructor == Person。
function Person() {
}
person1.constructor == Person
Person.prototype.constructor == Person
function Person() {
}
var person = new Person();
console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
person1 为什么有 constructor 属性?那是因为 person1 是 Person 的实例。
那 Person.prototype 为什么有 constructor 属性??同理, Person.prototype (你把它想象成 A) 也是Person 的实例。也就是在 Person 创建的时候,创建了一个它的实例对象并赋值给它的 prototype
结论:原型对象(Person.prototype)是 构造函数(Person)的一个实例。
实例与原型
function Person() {
}
Person.prototype.name = 'Kevin';
var person = new Person();
person.name = 'Daisy';
console.log(person.name) // Daisy
delete person.name;
console.log(person.name) // Kevin
在这个例子中,我们给实例对象 person 添加了 name 属性,当我们打印 person.name 的时候,结果自然为 Daisy。
但是当我们删除了 person 的 name 属性时,读取 person.name,从 person 对象中找不到 name 属性就会从 person 的原型也就是 person.proto ,也就是 Person.prototype中查找,幸运的是我们找到了 name 属性,结果为 Kevin。
原型与原型
(1)当对象为普通对象时:
var obj = new Object();
obj.name = 'Kevin'
console.log(obj.name) // Kevin
obj.__proto__===Object.prototype;
Object.prototype.constructor===Object;
Object.prototype.__proto__===null;
(2)当对象为函数对象时:
function Person() {
}
var person = new Person();
person.__proto__===Person.prototype;
Person.prototype.constructor===Person;
Person.__proto__===Function.prototype;
Person.prototype.__proto__ === Object.prototype;
Function.prototype.__proto__===Object.prototype;
Function.prototype.constructor===Function;
Object.prototype.__proto__===null;
Object.prototype.constructor===Object;
原型链
console.log(Object.prototype.__proto__ === null) // true
JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些
函数内部定义数据和原型实例上定义数据区别:
1.原型上定义实例可以实现共享(形成内部的全局)
function Animal(type,name) {
this.name = name;
this.type = type;
this.tell = function() {
console.log('hello')
}
}
var dog = new Animal('dog','lunky');
var cat = new Animal('cat','miao');
dog.tell === cat.tell//false
function Animal(type,name) {
this.name = name;
this.type = type;
}
var dog = new Animal('dog','lunky');
var cat = new Animal('cat','miao');
Animal.prototype.tell = function() {
console.log('hello')
}
dog.tell === cat.tell//true
在函数内部的参数对于不同的实例对象是不同的,而在原型实例上的参数是可以共用的。
2.消耗性能
构造函数中的方法每当new一个对象的时候,就会创建一个构造函数里的方法,如果多个实例对象就会创建多个方法,占用内存,没有提高代码的复用性
将方法定义到构造函数的原型对象里,创建多个实例对象而共享一个方法,方法创建了一次。
比如第一点dog和cat都拥有tell这个函数,但是他们开辟的地址不同,如果我new了一百个实例,那么将会开辟一百个不同的地址,极大的浪费性能,而原型实例只会有一个。
3.定义优先级不同
function Animal(type,name) {
this.name = name;
this.type = type;
this.tell = function() {
console.log('hello')
}
}
Animal.prototype.tell = function() {
console.log('hello world')
}
var dog = new Animal('dog','lunky');
dog.tell //hello
function Animal(type,name) {
this.name = name;
this.type = type;
}
var cat = new Animal('cat','miao');
Animal.prototype.tell = function() {
console.log('hello world')
}
cat.tell //hello world
构造函数中定义的属性和方法要比原型中定义的属性和方法的优先级高,如果定义了同名称的属性和方法,构造函数中的将会覆盖原型中的。可以通过dog.prototype取到。
练习
function Foo(){
Foo.a=function(){
console.log(1);
}
this.a=function(){
console.log(2);
}
}
Foo.prototype.a=function(){
console.log(3);
}
Foo.a=function(){
console.log(4);
}
Foo.a();//4
let obj = new Foo();
obj.a();//2
Foo.a();//1
Foo.a()===>4,Foo.a是构造函数Foo()内部定义的私有方法,不在原型链上。因为function Foo(){}只是定义并没有执行,所以是根据Foo.a=function(){console.log(4);}。
obj.a()===>2,因为在new的时候会执行一次构造函数,并且构造函数的方法和变量会被新的对象继承,所以obj对象继承this.a这个方法,由于Foo.a是赋值给构造函数Foo()的,所以会打印2;关于为何没有打印3,是由于寻值会优先在当前obj对象上寻找,如果没有再往上。
Foo.a()===>1,因为在new的时候会执行一次构造函数,所以Foo.a被新的函数覆盖,打印1。