JS 原型与原型链简单理解

168 阅读4分钟

关键词:继承,__proto__,prototype,constructor,instanceof,call(apply)。。。

(一)普通对象与函数对象

JavaScript 中,万物皆对象!但对象也是有区别的。分为普通对象和函数对象,

function F () {};

F是函数对象;Object 、Function、Date、Number等是 JS 自带的函数对象;

因为通过new Function()的方式进行创建的都是函数对象,包括Object 、Function也不例外。

var o1 = {}, o2 = new Object(), f = new F();

o1、o2、f为普通对象;

(二)constructor(构造函数属性)

constructor 属性是专门为 function 而设计,它存在于每一个 function 的prototype(原型对象) 属性中。这个 constructor 保存了指向 function 的一个引用。

在定义一个函数F时,JavaScript 内部会执行如下几个动作: 1.为函数F添加一个原形对象属性(即 F.prototype); 2. 为 原形对象 F.prototype 额外添加一个 constructor 属性(即 F.prototype.constructor),并且该属性保存指向函数 F 的一个引用;

即, F.prototype.constructor === F ;(true)

引申:Object.prototype.constructor === Object;Function.prototype.constructor == Function; Number.prototype.constructor == Number;

构造函数还有一层意义,f是通过new F()得到的对象,所以有f.constructor === F,即f的构造函数是F

(三)prototype(原型对象)

每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性(上面说到),这个属性指向函数的原型对象。

即,F.prototype 就是F的原型对象

原型对象,顾名思义,它就是一个普通对象。

但有一个例外,Function.prototype 是一个函数对象,为啥?

可以这么理解,(二)中提到Function.prototype.constructor == Function;那么其实Function.prototype通过new Function()得到的,(一)中说到通过new Function()创建的都是函数对象。

Function.prototype也是一个例外,这个函数对象没有原型对象(prototype),因为Function.prototype是一个空函数。

Function.prototype也是唯一一个typeof XXX.prototype为 function的prototype

typeof F.prototype === 'Object' ;(true)

typeof Object.prototype === 'Object' ;(true)

typeof Number.prototype === 'Object' ;(true)

typeof Function.prototype === 'function' ;(true)

原型对象是用来做什么的呢?主要作用是用于继承。

// 定义一个类
function Person(name){
  this.name = name || 'Person';
}
Person.prototype.getName = function(){
  console.log(this.name);
}
//实例化
var person1 = new Person('Lee');

person1是Person的一个实例,那么person1就继承了Person的原型对象Person.prototype的属性

所以,person1.getName() ; //Lee

person1的构造函数是Person;

(四)__proto__(原型链)

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

所有函数对象的__proto__都指向Function.prototype,

person1.__proto__ === Person.prototype;(true)

Person.__proto__ === Function.prototype;(true)

Number.__proto__ === Function.prototype;(true)

Object.__proto__ === Function.prototype;(true)

Function.__proto__ === Function.prototype;(true)

所以原型链存在于实例person1与构造函数Person的原型对象Person.prototype之间,而不是存在于实例与构造函数之间,即(三)中说的person1继承了Person.prototype的getName方法;

Person.prototype.__proto__ 、Number.prototype.__proto__、Function.prototype.__proto__、Object.prototype.__proto__是什么?

Person.prototype、Number.prototype、Function.prototype都是普通对象,无需关注它有哪些属性,只要记住它是一个普通对象。普通对象的构造函数 === Object,所以,

Person.prototype.__proto__ === Object.prototype;(true)

Number.prototype.__proto__ === Object.prototype;(true)

Function.prototype.__proto__ === Object.prototype;(true)

Object.prototype 比较特殊,但它也有__proto__属性,为null。null处于原型链顶端(万物皆空)!!!!

上图:

(五)call(apply)

每个函数都包含两个非继承而来的方法:call()方法和apply()方法,作用是改变函数内部this的指向,两个方法作用是一样的,区别仅是传参方式不同。

var foo = {

  name:"foo",
  logName:function(){
    console.log(this.name);
  }
}
var bar={
  name:"bar"
};
foo.logName();//foo
foo.logName.call(bar);//bar
call或者apply通过改变函数内部this的指向,可以帮助我们实现继承

(六)继承

组合继承:通过call(apply)调用父类构造函数,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。举例说明:

// 定义一个动物类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};
//实现继承
function Cat(name){
  Animal.call(this,name);
  // 子类扩展方法
  this.climb = function(){
    console.log(this.name + '会爬树!');
  }
}
Cat.prototype = new Animal();//将父类实例作为子类原型,实现函数复用,这样Cat.prototype继承了Animal.prototype的所有方法
// 所以Cat的实例就继承了Animal.prototype的所有方法.
Cat.prototype.constructor = Cat;//修改构造函数属性(constructor)的指向,上面操作之后使得Cat.prototype成为了Animal的实例,
// 所以 Cat.prototype.constructor == Animal,但是原本在创造Cat的时候Cat.prototype.constructor === Cat,
// 所以这里修改回来,这样Cat的实例的构造函数才能指向Cat.
 
var cat = new Cat('Black Cat'); // new一个Cat的实例
console.log(cat.name);//Black Cat
cat.sleep();//Black Cat正在睡觉!
cat.eat('鱼');//Black Cat正在吃:鱼
cat.climb();//Black Cat会爬树!
console.log(cat.constructor === Cat);//true

(七)instanceof

instanceof就是JavaScript 中一个运算符,用来判断一个构造函数的prototype属性所指向的对象是否存在另外一个要检测对象的原型链

因为 cat.__proto__ === Cat.prototype

=> console.log(cat indstanceof Cat);//true

Cat.prototype是Animal一个实例 => Cat.prototype.__proto__ === Animal.prototype

=> console.log(cat indstanceof Animal);//true

Animal.prototype.__proto__ === Object.prototype

=> console.log(cat indstanceof Object);//true

然后 Object.prototype === null,到顶了