原型及原型链是javascript中非常重要的东西,对看别人源码和自己设计框架和深入理解javascript这门语言特别有用。
prototype 每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向了一个对象,而这个对象的用途就是包含可以由特定类型的所有实例共享的属性和方法。
proto 所有引用类型(函数,数组,对象)都拥有__proto__属性。JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype )。
proto 属性是JavaScript 的非标准但许多浏览器实现的属性__proto__
原型对象 拥有prototype属性的对象,在定义函数时就被创建
原型是Javascript中的继承的基础,JavaScript的继承就是基于原型的继承。
Javascript的继承机制基于原型,而不是Class类。
function fun1(){};
let fun2 = function(){};
let fun3 = new Function('content','console.log(content)');
let obj1 = new fun1();
let obj2 = {};
let obj3 =new Object();
console.log(typeof Object); //function
console.log(typeof Function); //function
console.log(typeof Array); //function
// Object Function和 Array 都是函数
console.log(typeof obj1); //object
console.log(typeof obj2); //object
console.log(typeof obj3); //object
console.log(typeof fun1); //function
console.log(typeof fun2); //function
console.log(typeof fun3); //function
函数也是一种对象。它是属性的集合。每个函数都有一个属性叫做prototype。
这个prototype的属性值是一个对象(属性的集合,再次强调!),默认的只有一个叫做constructor的属性,指向这个函数本身。
原型既然作为对象,属性的集合,不可能就只弄个constructor来玩玩,肯定可以自定义的增加许多属性。我们看下Object,它的prototype里面,就有好几个其他属性。
下面我们来看一下自己定义函数的原型
function Person(){
}
Person对象会自动获得prototyp属性,而prototype也是一个对象,会自动获得一个constructor属性,该属性正是指向Person对象。我们看一下Person的原型
自己自定义的方法的prototype中新增自己的属性
function Person(){
}
Person.prototype.name = 'Jason';
Person.prototype.getYear = function(){
return 1989;
}
那原型对象是用来做什么的呢?主要作用是用于继承
我们来看下面的代码
function Person(){
}
Person.prototype.name = 'Jason';
Person.prototype.getYear = function(){
return 1989;
}
let person = new Person();
console.log(person.name); //Jason
console.log(person.getYear()); //1989
我们来看一下增加了原型方法和属性后的原型
从上面可以看出,通过给Person.prototype设置了一个函数对象的属性,那有Person实例对象(person)出来的普通对象就继承了这个属性。具体是怎么实现的继承,就要讲到下面的原型链了
我们上面的例子,创建了person同时,自动生成一个__proto__属性,该属性指向Person的prototype,可以访问到prototype内定义的getYear方法.
person.__proto__ === Person.prototype //true
我们直接上代码
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.personArray = [];
let person1 = new Person('Jason', 29);
let person2 = new Person('Peter', 5);
person1.personArray.push(1);
person2.personArray.push(2);
console.log(person1.personArray); //[1,2]
因此当代码读取某个对象的某个属性的时候,都会执行一遍搜索,目标是具有给定名字的属性,搜索首先从对象实例开始,如果在实例中找到该属性则返回,如果没有则查找prototype,如果还是没有找到则继续递归prototype的prototype对象,直到找到为止,如果递归到object仍然没有则返回undefine。同样道理如果在实例中定义如prototype同名的属性或函数,则会覆盖prototype的属性或函数(其它高级语言的重写)。—-这就是Javascript的原型链。
我们还是拿上面的例子
function Person(){
}
Person.prototype.name = 'Jason';
Person.prototype.getYear = function(){
return 1989;
}
let person = new Person();
console.log(person.__proto__ === Person.prototype) //true
console.log(Person.prototype.__proto__=== Object.prototype) //true
console.log(Object.prototype.__proto__) //null
4.这个有__proto__串起来的直到Object.prototype.__proto__为null的链叫做原型链,因此person的原型链如下:
当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,到查找到达原型链的顶部 - 也就是 Object.prototype - 但是仍然没有找到指定的属性,就会返回 undefined
var o = {a: 1};
// o 这个对象继承了 Object.prototype 上面的所有属性
// o 自身没有名为 hasOwnProperty 的属性
// hasOwnProperty 是 Object.prototype 的属性
// 因此 o 继承了 Object.prototype 的 hasOwnProperty
// Object.prototype 的原型为 null
// 原型链如下:
// o ---> Object.prototype ---> null
var a = ["yo", "whadup", "?"];
// 数组都继承于 Array.prototype
// (Array.prototype 中包含 indexOf, forEach 等方法)
// 原型链如下:
// a ---> Array.prototype ---> Object.prototype ---> null
function f(){
return 2;
}
// 函数都继承于 Function.prototype
// (Function.prototype 中包含 call, bind等方法)
// 原型链如下:
// f ---> Function.prototype ---> Object.prototype ---> null
在 JavaScript 中,构造器其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。
function Graph() {
this.vertices = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function(v){
this.vertices.push(v);
}
};
var g = new Graph();
// g 是生成的对象,他的自身属性有 'vertices' 和 'edges'。
// 在 g 被实例化时,g.[[Prototype]] 指向了 Graph.prototype。
ECMAScript 5 中引入了一个新方法:Object.create()。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数:
var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为d没有继承Object.prototype
ECMAScript6 引入了一套新的关键字用来实现 class。使用基于类语言的开发人员会对这些结构感到熟悉,但它们是不同的。JavaScript 仍然基于原型。这些新的关键字包括 class, constructor,static,extends 和 super。
"use strict";
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
class Square extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength);
}
get area() {
return this.height * this.width;
}
set sideLength(newLength) {
this.height = newLength;
this.width = newLength;
}
}
var square = new Square(2);
在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。
遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从 Object.prototype 继承的 hasOwnProperty 方法。下面给出一个具体的例子来说明它:
console.log(g.hasOwnProperty('vertices'));
// true
console.log(g.hasOwnProperty('nope'));
// false
console.log(g.hasOwnProperty('addVertex'));
// false
console.log(g.__proto__.hasOwnProperty('addVertex'));
// true
hasOwnProperty 是 JavaScript 中唯一一个处理属性并且不会遍历原型链的方法。(译者注:原文如此。另一种这样的方法:Object.keys())
注意:检查属性是否为 undefined 是不能够检查其是否存在的。该属性可能已存在,但其值恰好被设置成了 undefined
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype = {
getPersonInfo: function(){
let info = `my name is ${this.name}, and i am ${this.age} years old`;
return info;
},
}
let person = new Person('Jason', 29);
person.getPersonInfo();
Person.prototype = function () { } ();
它的好处就是可以封装私有的function,通过return的形式暴露出简单的使用名称,以达到public/private的效果
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype = function(){
getPersonInfo = function(){
let info = `my name is ${this.name}, and i am ${this.age} years old`;
return info;
}
return {getPersonInfo:getPersonInfo}
}();
let person = new Person('Jason', 29);
person.getPersonInfo();
上述使用原型的时候,有一个限制就是一次性设置了原型对象,我们再来说一下如何分来设置原型的每个属性
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.getPersonInfo = function(){
let info = `my name is ${this.name}, and i am ${this.age} years old`;
return info;
}
let person = new Person('Jason', 29);
person.getPersonInfo();
我们来重写原型方法,在使用第三方JS类库的时候,往往有时候他们定义的原型方法是不能满足我们的需要,但是又离不开这个类库,所以这时候我们就需要重写他们的原型中的一个或者多个属性或function,我们可以通过继续声明的同样的
getPersonInfo代码的形式来达到覆盖重写前面的getPersonInfo功能
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.getPersonInfo = function(){
let info = `my name is ${this.name}, and i am ${this.age} years old`;
return info;
}
Person.prototype.getPersonInfo = function(){
return 'I am new getPersonInfo method';
}
let person = new Person('Jason', 29);
person.getPersonInfo();//I am new getPersonInfo method
这样,我们计算得出了后面函数的结果,但是有一点需要注意:那就是重写的代码需要放在最后,这样才能覆盖前面的代码