走在前端的大道上javascript
本篇将本身读过的相关 javascript原型和原型链 文章中,对本身有启发的章节片断总结在这(会对原文进行删改),会不断丰富提炼总结更新。前端
通常的初学者,在刚刚学习了基本的javascript语法后,都是经过面向函数来编程的。以下代码:java
var decimalDigits = 2, tax = 5; function add(x, y) { return x + y; } function subtract(x, y) { return x - y; } //alert(add(1, 3));
经过执行各个函数来获得最后的结果。可是利用原型,咱们能够优化一些咱们的代码,使用构造函数:
首先,函数本体中只存放变量:git
var Calculator = function (decimalDigits, tax) { this.decimalDigits = decimalDigits; this.tax = tax; };
其具体的方法经过prototype属性来设置:编程
Calculator.prototype = { add: function (x, y) { return x + y; }, subtract: function (x, y) { return x - y; } }; //alert((new Calculator()).add(1, 3));
这样就能够经过实例化对象后进行相应的函数操做。这也是通常的js框架采用的方法。segmentfault
原型还有一个做用就是用来实现继承。首先,定义父对象:框架
var BaseCalculator = function() { this.decimalDigits = 2; }; BaseCalculator.prototype = { add: function(x, y) { return x + y; }, subtract: function(x, y) { return x - y; } };
而后定义子对象,将子对象的原型指向父元素的实例化:编程语言
var Calculator = function () { //为每一个实例都声明一个税收数字 this.tax = 5; }; Calculator.prototype = new BaseCalculator();
咱们能够看到Calculator的原型是指向到BaseCalculator的一个实例上,目的是让Calculator集成它的add(x,y)和subtract(x,y)这2个function,还有一点要说的是,因为它的原型是BaseCalculator的一个实例,因此无论你建立多少个Calculator对象实例,他们的原型指向的都是同一个实例
。
上面的代码,运行之后,咱们能够看到由于Calculator的原型是指向BaseCalculator的实例上的,因此能够访问他的decimalDigits属性值,那若是我不想让Calculator访问BaseCalculator的构造函数里声明的属性值,那怎么办呢?只须要将Calculator指向BaseCalculator的原型
而不是实例就好了。代码以下:函数
var Calculator = function () { this.tax= 5; }; Calculator.prototype = BaseCalculator.prototype;
在使用第三方库的时候,有时候他们定义的原型方法不能知足咱们的须要,咱们就能够本身添加一些方法,代码以下:学习
//覆盖前面Calculator的add() function Calculator.prototype.add = function (x, y) { return x + y + this.tax; }; var calc = new Calculator(); alert(calc.add(1, 1));
原型链
对象的原型指向对象的父,而父的原型又指向父的父,这种原型层层的关系,叫作原型链。
在查找一个对象的属性时,javascript会向上遍历原型链,直到找到给定名称的属性为止,当查找到达原型链的顶部,也便是Object.prototype,仍然没有找到指定的属性,就会返回undefined。
示例以下:
function foo() { this.add = function (x, y) { return x + y; } } foo.prototype.add = function (x, y) { return x + y + 10; } Object.prototype.subtract = function (x, y) { return x - y; } var f = new foo(); alert(f.add(1, 2)); //结果是3,而不是13 alert(f.subtract(1, 2)); //结果是-1
咱们能够发现,subtrace是按照向上找的原则,而add则出了意外。缘由就是,属性在查找的时候是先查找自身的属性,若是没有再查找原型。
说到Object.prototype,就不得不提它的一个方法,hasOwnProperty。它能判断一个对象是否包含自定义属性而不是原型链上的属性,它是javascript中惟一一个处理属性可是不查找原型链的函数。使用代码以下:
// 修改Object.prototype Object.prototype.bar = 1; var foo = {goo: undefined}; foo.bar; // 1 'bar' in foo; // true foo.hasOwnProperty('bar'); // false foo.hasOwnProperty('goo'); // true
而为了判断prototype对象和某个实例之间的关系,又不得不介绍isPrototyleOf方法,演示以下:
alert(Cat.prototype.isPrototypeOf(cat2)); //true
JavaScript和Java、C++等传统面向对象的编程语言不一样,它是没有类(class)的概念的(ES6 中的class也只不过是语法糖,并不是真正意义上的类),而在JavaScript中,一切皆是对象(object)。在基于类的传统面向对象的编程语言中,对象由类实例化而来,实例化的过程当中,类的属性和方法会拷贝到这个对象中;对象的继承其实是类的继承,在定义子类继承于父类时,子类会将父类的属性和方法拷贝到自身当中。所以,这类语言中,对象建立和继承行为都是经过拷贝完成的。但在JavaScript中,对象的建立、对象的继承(更好的叫法是对象的代理,由于它并非传统意义上的继承)是不存在拷贝行为的。如今让咱们忘掉类、忘掉继承,这一切都不属于JavaScript。
其实,原型这个名字自己就很容易产生误解,原型在百度词条中的释义是:指原来的类型或模型。按照这个定义解释的话,对象的原型是对象建立自身的模子,模子具有的特色对象都要具备,这俨然就是拷贝的概念。咱们已经说过, JavaScript的对象建立不存在拷贝,对象的原型实际上也是一个对象,它和对象自己是彻底独立的两个对象。既然如此,原型存在的意义又是什么呢?原型是为了共享多个对象之间的一些共有特性(属性或方法),这个功能也是任何一门面向对象的编程语言必须具有的。A、B两个对象的原型相同,那么它们必然有一些相同的特征。
JavaScript中的对象,都有一个内置属性[[Prototype]],指向这个对象的原型对象。当查找一个属性或方法时,若是在当前对象中找不到定义,会继续在当前对象的原型对象中查找;若是原型对象中依然没有找到,会继续在原型对象的原型中查找(原型也是对象,也有它本身的原型);如此继续,直到找到为止,或者查找到最顶层的原型对象中也没有找到,就结束查找,返回undefined。能够看出,这个查找过程是一个链式的查找,每一个对象都有一个到它自身原型对象的连接,这些连接组成的整个链条就是原型链。拥有相同原型的多个对象,他们的共同特征正是经过这种查找模式体现出来的。
在上面的查找过程,咱们提到了最顶层的原型对象,这个对象就是Object.prototype,这个对象中保存了最经常使用的方法,如toString、valueOf、hasOwnProperty等,所以咱们才能在任何对象中使用这些方法。
1.字面量方式
当经过字面量方式建立对象时,它的原型就是Object.prototype。虽然咱们没法直接访问内置属性[[Prototype]],但咱们能够经过Object.getPrototypeOf()或对象的__proto__获取对象的原型。
var obj = {}; Object.getPrototypeOf(obj) === Object.prototype; // true obj.__proto__ === Object.prototype; // true
2.函数的构造调用
经过函数的构造调用(注意,咱们不把它叫作构造函数,由于JavaScript中一样没有构造函数的概念,全部的函数都是平等的,只不过用来建立对象时,函数的调用方式不一样而已)也是一种经常使用的建立对象的方式。基于同一个函数建立出来的对象,理应能够共享一些相同的属性或方法,但这些属性或方法若是放在Object.prototype里,那么全部的对象均可以使用它们了,做用域太大,显然不合适。因而,JavaScript在定义一个函数时,同时为这个函数定义了一个 默认的prototype属性,全部共享的属性或方法,都放到这个属性所指向的对象中。由此看出,经过一个函数的构造调用建立的对象,它的原型就是这个函数的prototype指向的对象。
var f = function(name) { this.name = name }; f.prototype.getName = function() { return this.name; } //在prototype下存放全部对象的共享方法 var obj = new f('JavaScript'); obj.getName(); // JavaScript obj.__proto__ === f.prototype; // true
//建立构造函数 function Person(name){ this.name = name } //每一个构造函数JS引擎都会自动添加一个prototype属性,咱们称之为原型,这是一个对象 //每一个由构造函数建立的对象都会共享prototype上面的属性与方法 console.log(typeof Person.prototype) // 'object' //咱们为Person.prototype添加sayName方法 Person.prototype.sayName = function(){ console.log(this.name) } //建立实例 var person1 = new Person('Messi') var person2 = new Person('Suarez') person1.sayName() // 'Messi' person2.sayName() // 'Suarez' person1.sayName === person2.sayName //true
咱们借助上面的例子来理解构造函数-原型-实例,三者之间的关系,主要有几个基本概念
Person.prototype.constructor === Person
person1.__proto__ === Person.prototype person1.__proto__.constructor === Person
Oject自己是一个构造函数,它也是一个对象,那么
Object.__proto__ === Function.prototype
还有几个须要咱们知道的特殊概念:
Object.prototype.__proto__ === null
3.Object.create()
第三种经常使用的建立对象的方式是使用Object.create()。这个方法会以你传入的对象做为建立出来的对象的原型。
var obj = {}; var obj2 = Object.create(obj); obj2.__proto__ === obj; // true
这种方式还能够模拟对象的“继承”行为。
function Foo(name) { this.name = name; } Foo.prototype.myName = function() { return this.name; }; function Bar(name,label) { Foo.call( this, name ); // this.label = label; } // temp对象的原型是Foo.prototype var temp = Object.create( Foo.prototype ); // 经过new Bar() 建立的对象,其原型是temp, 而temp的原型是Foo.prototype, // 从而两个原型对象Bar.prototype和Foo.prototype 有了"继承"关系 Bar.prototype = temp; Bar.prototype.myLabel = function() { return this.label; }; var a = new Bar( "a", "obj a" ); a.myName(); // "a" a.myLabel(); // "obj a" a.__proto__.__proto__ === Foo.prototype; //true
这是容易混淆的两个属性。__proto__指向当前对象的原型,prototype是函数才具备的属性,默认状况下,new 一个函数建立出的对象,其原型都指向这个函数的prototype属性。
1.对于JavaScript中的内置对象,如String、Number、Array、Object、Function等,由于他们是native代码实现的,他们的原型打印出来都是ƒ () { [native code] }。
2.内置对象本质上也是函数,因此能够经过他们建立对象,建立出的对象的原型指向对应内置对象的prototype属性,最顶层的原型对象依然指向Object.prototype。
'abc'.__proto__ === String.prototype; // true new String('abc').__proto__ === String.prototype; //true new Number(1).__proto__ ==== Number.prototype; // true [1,2,3].__proto__ === Array.prototype; // true new Array(1,2,3).__proto__ === Array.prototype; // true ({}).__proto__ === Object.prototype; //true new Object({}).__proto__ === Object.prototype; // true var f = function() {}; f.__proto__ === Function.prototype; // true var f = new Function('{}'); f.__proto__ === Function.prototype; // true
3.Object.create(null) 建立出的对象,不存在原型。
var a = Object.create(null); a.__proto__; // undefined
此外函数的prototype中还有一个constructor方法,建议你们就当它不存在,它的存在让JavaScript原型的概念变得更加混乱,并且这个方法也几乎没有做用。