原型链其实是JavaScript中的实现继承的机制,在搞懂原型链以前首先要搞懂几个概念:this,普通对象和函数对象,构造函数,new编程
this对于不少人来讲是混杂不清的概念,可是想要弄清楚原型链咱们必须了解什么是this数组
首先,this只能在存在与函数中函数
其次,this实际上是当前函数所在上下文环境,再简单一点也能够理解为this返回一个当前函数所在的对象,也就是说想要知道this是什么咱们只须要关注是谁调用了this所在的函数就能够了学习
以下边的代码zhao.sayName()是zhao调用的sayName函数因此sayName中的this天然指的就是zhao这个对象,而下方 var liName = zhao.sayName语句是将sayName这个函数赋值给liName,调用liName就至关于在最顶层也就是window下直接调用sayName,this的指向天然就是window这个最顶层对象this
var name = "Li" var zhao = { name: "Zhao", sayName: function () { console.log(this.name); } } zhao.sayName() // Zhao var liName = zhao.sayName; liName() // Li
JavaScript中一切均可以看做对象,可是实际上对象也是有区别的,对象分为普通对象和函数对象spa
// 普通对象 var o1 = {} var o2 = new Object() var o3 = new f1() // 函数对象 function f1(){} var f2 = function(){} var f3 = new Function() console.log(typeof f1); //function console.log(f1.prototype); //true console.log(typeof f2); //function console.log(f2.prototype); //true console.log(typeof f3); //function console.log(f3.prototype); //true console.log(typeof o1); //object console.log(o1.prototype); //undefined console.log(typeof o2); //object console.log(o2.prototype); //undefined console.log(typeof o3); //object console.log(o3.prototype); //undefined
凡是经过function构建的对象都是函数对象,而且只有函数对象才有prototype属性,普通对象没有prototype
prototype又是什么呢?设计
当咱们建立函数的时候,编译器会自动为该函数建立一个prototype属性,这和属性指向一个包含constructor属性的对象,而这个属性又默认指回原函数,读起来有点绕对吧,大概是这样的指针
function Person() { // prototype = { // constructor: Person, // } }
每一个函数对象都有一个prototype(原型)属性,在我看来prototype属性的意义:code
这两点等学习了下边new命令你就会明白了
函数对象的一种用法就是构造函数,经过构造函数能够构建一个函数对象的实例(普通对象)
function Person(name, age ){ this.name = name; this.age = age; this.sayHello = function(){ console.log(`Hello! my name is ${this.name}`); }; } var person1 = new Person("kidder", 28); person1.sayHello(); // Hello! my name is kidder console.log(person1.constructor); //[Function:Person]
按照惯例,构造函数的命名以大写字母开头,非构造函数以小写字母开头,经过构造函数构造的普通对象都会有一个constructor(构造函数)属性,该属性指向构造该对象的构造函数
下面咱们来看看构造函数构建一个普通对象的时候发生了什么
var Person = function (name) { this.name = name; this.age = 18; }; Person.prototype.sayHello = function(){ console.log(`Hello! my name is ${this.name}`); }; var li = new Person("Li"); console.log(li.name); // Li console.log(li.age); // 18 li.sayHello(); // Hello! my name is Li
{}
{ __proto__:Person.prototype; }
this = { __proto__:Person.prototype; }
执行构造函数内部的代码
this = { __proto__:Person.prototype; name: "Li"; age: 18; }
因此li这个对象中只有name和age两个属性,为何li.sayHello()会输出Hello! my name is Li呢?
这就是原型链,当给定的属性在当前对象中找不到的状况下,会沿着__proto__这个属性一直向对象的上游去寻找,直到__proto__这个属性指向null为止,若是找到指定属性,查找就会被截断,中止
上面这张图是整个JavaScript的原型链体系,为了让这张图更直观因此我将构造函数的prototype属性单独提了出来,恩,其实画在构造函数内部也可,但同时由于对象是引用类型,因此这样画也没毛病吧
这两个属性常常会被咱们混淆,那么咱们回过头再来总结一下
prototype:只有函数对象才具备的属性,它用来存放的是构造函数但愿构建的实例具备的共享的属性和方法,主要用于构造函数的实例化
_proto_ : 全部对象都具备的属性,它指向的是当前对象在原型链上的上级对象,主要做用是让编译器在由__proto__这个属性构成的原型链上查找特定的属性和方法
var Person = function (name) { this.name = name; this.age = 18; }; Person.prototype.sayHello = function(){ console.log(`Hello! my name is ${this.name}`); }; var li = new Person("Li"); var Person1 = function () { }; Person.prototype.name = "Li" Person.prototype.age = 18 Person.prototype.sayHello = function(){ console.log(`Hello! my name is ${this.name}`); }; var Li = new Person1();
关于Person和Person1两种构造函数的写法有什么不一样呢?
通常来讲写在prototype原型对象中的属性和方法都是公用的,也就是说写在构造函数中的属性在构建普通对象的时候,都会在新对象中从新定义,也就是从内存的角度来讲又会多占用一些内存空间,因此咱们将构造函数的全部属性和方法都写在prototype原型中很差吗?
可是原型函数也是有缺点的:
var Person = function () { }; Person.prototype.name = "Li" Person.prototype.age = 18 Person.prototype.sayHello = function(){ console.log(`Hello! my name is ${this.name}`); }; var li = new Person(); var zhao = new Person();
这种方式构造的全部对象都是一个模板,虽然咱们也能够在当前对象下进行修改,但这样一点也不优雅,不规整,并且从某种意义上来讲也是对内存的浪费
var Person = function () { }; Person.prototype.name = "Li" Person.prototype.age = 18 Person.prototype.friends = ["ZhangSan", "LiSi"] Person.prototype.sayHello = function(){ console.log(`Hello! my name is ${this.name}`); }; var li = new Person(); var zhao = new Person(); li.friends.push("WangWu"); console.log(zhao.friends); // [ 'ZhangSan', 'LiSi', 'WangWu' ]
在JavaScript中,基本类型的修改能够明确的经过建立或修改在当前对象下的属性对原型链进行截断,可是像数组,对象这种引用类型的值虽然也能够经过在当前对象中建立该属性来对原型链进行截断,可是一不注意就可能会出现上面这种状况直接对原型进行了修改
因此,用构造函数来定义实例属性,用原型定义方法和共享的属性,这样写就比较优雅了
function Person(name, age){ this.name = name; this.age = age; this.friends = ["ZhangSan", "LiSi"]; } Person.prototype.sayHello = function(){ console.log(`Hello! my name is ${this.name},${this.age}岁了`); }; var li = new Person("li", 18); var zhao = new Person("zhao", 16); li.sayHello(); // Hello! my name is li, 18岁了 zhao.sayHello(); // Hello! my name is zhao,16岁了 li.friends.push("WangWu"); console.log(zhao.friends); // [ 'ZhangSan', 'LiSi' ]
法一用构造函数构造一个新对象
var A = function () { }; var a = new A(); console.log(a.constructor); // [Function:A] console.log(a.__proto__ === A.prototype); //true
法二的本质来讲和法一是同样的,就是隐式调用原生构造函数Object来构造新对象
var a = {}; // var a = new Object(); console.log(a.constructor); // [Function:Object] console.log(a.__proto__ === Object.prototype); //true
法三Object.create是以一个普通对象为模板建立一个新对象
var a1 = {a:1} var a2 = Object.create(a1); console.log(a2.constructor); // [Function:Object] console.log(a2.__proto__ === a1);// true console.log(a2.__proto__ === a1.prototype); //false
因此除了Object.create建立对象的方式,能够说:__ proto __ === constructor.prototype;
前面咱们说道prototype的时候进行原型属性的赋值的时候,采用的是逐项赋值,那么当我直接将对象赋值给prototype属性的时候会发生什么呢?
function Person() { } Person.prototype = { name : "Li", age : 18, sayHello : function () { console.log(`Hello! my name is ${this.name},${this.age}岁了`); } }; var li = new Person(); console.log(li instanceof Object); // true console.log(li instanceof Person); // true console.log(li.constructor === Person); // false console.log(li.constructor === Object); // true console.log(Person.prototype.constructor); // Object
这时候咱们就发现咱们构建的li对象的constructor再也不指向它的构造函数Person,而是指向了Object,而且Person原型Person.prototype的constructor指向也指向了Object,这是什么缘由呢?
其实,根源出如今Person.prototype上,上边咱们提到过,其实咱们在写构造函数的时候其实是这样的
function Person() { // prototype = { // constructor : Person // } }
当咱们构建Person构造函数的时候,编译器会自动生成一个带有指向Person的constructor属性的对象,并把这个对象赋值给Person.prototype,咱们又知道js中对象是引用类型,当咱们使用Person.prototype.name=...的时候其实是对这个对象的修改,而使用Person.prototype={...}其实是将这个属性本来的指针指向了另外一个新建立的对象而不是原来编译器自动建立的那个:
而li的constructor属性天然是继承自Person.prototype,因此constructor天然也就跟着改变了,若是在编程的过程当中constructor这个属性很重要的话能够经过下面的方式
function Person() { } Person.prototype = { constructor:Person name : "Li", age : 18, sayHello : function () { console.log(`Hello! my name is ${this.name},${this.age}岁了`); } }; var li = new Person(); console.log(li instanceof Object); // true console.log(li instanceof Person); // true console.log(li.constructor === Person); // true console.log(li.constructor === Object); // false console.log(Person.prototype.constructor); // Person
参考:《JavaScript高级程序设计》
这是我对JS原型链部分的总结与思考,也是我写的第一篇这么正式的技术文档,若有纰漏之处,欢迎你们批评指正