深入理解javascript原型及原型链

原型及原型链是javascript中非常重要的东西,对看别人源码和自己设计框架和深入理解javascript这门语言特别有用。

原型及原型链名词解释

prototype 每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向了一个对象,而这个对象的用途就是包含可以由特定类型的所有实例共享的属性和方法。

proto 所有引用类型(函数,数组,对象)都拥有__proto__属性。JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype )。
proto 属性是JavaScript 的非标准但许多浏览器实现的属性__proto__

原型对象 拥有prototype属性的对象,在定义函数时就被创建

prototype原型

原型是Javascript中的继承的基础,JavaScript的继承就是基于原型的继承。
Javascript的继承机制基于原型,而不是Class类。

  1. 函数和对象的关系
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 
  1. 原型

函数也是一种对象。它是属性的集合。每个函数都有一个属性叫做prototype。
这个prototype的属性值是一个对象(属性的集合,再次强调!),默认的只有一个叫做constructor的属性,指向这个函数本身。
原型既然作为对象,属性的集合,不可能就只弄个constructor来玩玩,肯定可以自定义的增加许多属性。我们看下Object,它的prototype里面,就有好几个其他属性。
深入理解javascript原型及原型链_第1张图片

下面我们来看一下自己定义函数的原型

function Person(){
	
}

Person对象会自动获得prototyp属性,而prototype也是一个对象,会自动获得一个constructor属性,该属性正是指向Person对象。我们看一下Person的原型
深入理解javascript原型及原型链_第2张图片

自己自定义的方法的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

我们来看一下增加了原型方法和属性后的原型
深入理解javascript原型及原型链_第3张图片
从上面可以看出,通过给Person.prototype设置了一个函数对象的属性,那有Person实例对象(person)出来的普通对象就继承了这个属性。具体是怎么实现的继承,就要讲到下面的原型链了

  1. __proto__属性

我们上面的例子,创建了person同时,自动生成一个__proto__属性,该属性指向Person的prototype,可以访问到prototype内定义的getYear方法.

person.__proto__ === Person.prototype //true
  1. prototype内属性、方法是能够共享

我们直接上代码

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的原型链

原型链

原型链基础

  1. JS在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做_ proto _的内置属性,用于指向创建它的函数对象的原型对象prototype。

我们还是拿上面的例子

function Person(){
	
}
Person.prototype.name = 'Jason';
Person.prototype.getYear = function(){
	return 1989;
}

let person = new Person();
console.log(person.__proto__ === Person.prototype) //true
  1. Person.prototype对象也有__proto__属性,它指向创建它的函数对象(Object)的prototype
console.log(Person.prototype.__proto__=== Object.prototype) //true
  1. Object.prototype对象也有__proto__属性,但它比较特殊,为null
console.log(Object.prototype.__proto__) //null

4.这个有__proto__串起来的直到Object.prototype.__proto__为null的链叫做原型链,因此person的原型链如下:
深入理解javascript原型及原型链_第4张图片

  1. 原型链中属性查找

当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,到查找到达原型链的顶部 - 也就是 Object.prototype - 但是仍然没有找到指定的属性,就会返回 undefined

使用不同的方法来创建对象和生成原型链(此内容来自于MDN)

  1. 使用语法结构创建的对象
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
  1. 使用构造器创建的对象

在 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。
  1. 使用 Object.create 创建的对象

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
  1. 使用 class 关键字创建的对象

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);
  1. 性能

在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。

遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。要检查对象是否具有自己定义的属性,而不是其原型链上的某个属性,则必须使用所有对象从 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

原型的使用

原型的使用方式

  1. 通过给Person对象的prototype属性赋值对象字面量来设定Person对象的原型
    我们还是使用上面的例子
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();
  1. 在赋值原型prototype的时候使用function立即执行的表达式来赋值
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

这样,我们计算得出了后面函数的结果,但是有一点需要注意:那就是重写的代码需要放在最后,这样才能覆盖前面的代码

你可能感兴趣的:(javascript,前端进阶,javascript原型,javascript原型链,javascript,javascript,prototype)