原型 原型链

96 阅读6分钟

一切都是对象

当然,也不是所有的都是对象,值类型就不是对象。

 

typeof用来判断类型:

function show(x) {
  console.log(typeof x);    // undefined
  console.log(typeof 10);   // number
  console.log(typeof 'abc'); // string
  console.log(typeof true);  // boolean

  console.log(typeof function () { });  // function

  console.log(typeof [1, 'a', true]);  // object
  console.log(typeof { a: 10, b: 20 });  // object
  console.log(typeof null);  // object
  console.log(typeof new Number(10));  // object
}
show();

undefined、number、string、boolean 是值类型,不是对象。

函数、数组、对象、null、new Number(10) 是引用类型,都是对象。

typeof判断对象,不能区分 Object、Array、Null,都会显示object(除了函数)

判断一个变量是不是对象:值类型的判断用 typeof,引用类型的类型判断用 instanceof。

var fn = function () {};
console.log(fn instanceof Object);  // true

对象都是通过函数创建的

对象都是通过函数创建的 有些人可能反驳,因为:

var obj = { a: 10, b: 20 };
var arr = [5, 'x', true];

这个是一种“快捷方式”,在编程语言中叫做“语法糖”。

以上代码的本质是:

// var obj = { a: 10, b: 20 };
// var arr = [5, 'x', true];
var obj = new Object();
obj.a = 10;
obj.b = 20;
var arr = new Array();
arr[0] = 5;
arr[1] = 'x';
arr[2] = true;

而其中的 Object 和 Array 都是函数:

console.log(typeof (Object));  // function
console.log(typeof (Array));  // function

所以说 — 对象都是通过函数来创建的。

插一句-创建对象有几种方法

1.创建Object实例,再添加属性和方法

var person = new Object()
person.name = 'tom'

2.使用对象字面量模式

var person = {
	name:'tom'
}

3.使用构造函数

function Person(name,age){
	this.name = name
	this.age = age
}
var person1 = new Person('tom',21)

4.Object.create

Object.create(proto,[propertiesObject])

参数 proto 表示新创建对象的原型对象,必填。

var a = { rep: 'apple' }
var b = Object.create(a)
console.log(b)  // {}
console.log(b.__proto__) // {rep: "apple"}
console.log(b.rep) // apple

prototype原型

函数也是一种对象,也是属性的集合,可以对函数进行自定义属性。

每个函数都有一个属性叫做 prototype,prototype 的属性值是一个对象(属性的集合,再次强调!),默认的只有一个叫做 constructor 的属性,指向这个函数本身。

prototype 原型既然作为对象,属性的集合,不可能就只有 constructor,肯定可以自定义的增加许多属性。例如 Object 的 prototype 就有好几个其他属性。

可以在自定义方法的 prototype 中新增自己的属性

function Fn() {}
Fn.prototype.name = '王福朋';
Fn.prototype.getYear = function () {
  return 1988;
};

__proto__隐式原型

之前说 每个函数 function 都有一个 prototype 属性,即原型。

而 每个对象都有一个 proto 属性,即隐式原型,指向 创建该对象的函数 的 prototype。

obj 对象是被 Object 函数创建的,因此 obj.proto === Object.prototype。我们可以用一个图来表示。

Object.prototype 是一个特例,它的 proto 指向的是 null

 

函数也是一种对象,也是被创建出来的。谁创建了函数呢?Function 注意这个大写的“F”。

以上代码中,第一种方式是比较传统的函数创建方式,第二种是用new Functoin创建。

不推荐用第二种方式,只是演示函数是被 Function 创建的。

 

就会出现:

Foo.proto === Function.prototype

Object.proto === Function.prototype

Function.proto === Function.prototype

 

Function.prototype 也是对象,它的 proto 指向 Object.prototype

即:Function.prototype._proto === Object.prototype

原型链

简单总结

1、prototype函数都有这个属性,指向原型对象****

2、constructor: 原型对象默认获得这个属性,指向构造函数

3、proto: 对象( null 除外)都有这个属性 ,指向构造函数的原型对象

tips 函数也是一个对象,也有 proto 属性

原型链完整图:

  • Object是所有对象的爸爸,所有对象都可以通过__proto__找到它
  • Function是所有函数的爸爸,所有函数都可以通过__proto__找到它
  • Function.prototypeObject.prototype是两个特殊的对象,他们由引擎来创建
  • 除了以上两个特殊对象,其他对象都是通过构造器new出来的
  • 函数的prototype是一个对象,也就是原型
  • 对象的 __proto__ 属性指向原型,__proto__ 将对象和原型连接起来组成了原型链

instanceof 原理

用 instanceof 来判断引用类型变量,判断规则就是根据原型链:

A instanceof B:沿着 A 的 proto 这条线来找,同时沿着 B 的 prototype 这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回 true。如果找到终点还未重合,则返回 false。

按照以上规则,“ f1 instanceof Object ”返回的是 true

可以解释很多比较怪异的现象:

手写 instanceof

function instance_of(L, R) { // L 表示左表达式,R 表示右表达式
 var O = R.prototype; // 取 R 的显示原型
 L = L.__proto__; // 取 L 的隐式原型
 while (true) { 
   if (L === null) 
     return false; 
   if (O === L) // 这里重点:当 O 严格等于 L 时,返回 true 
     return true; 
   L = L.__proto__; 
 } 
}

测试

1、person1.proto 是什么?

2、Person.proto 是什么?

3、Person.prototype.proto 是什么?

4、Object.proto 是什么?

5、Object.prototype.proto 是什么?

答案:

第一题:

因为 person1.proto === person1 的构造函数.prototype

所以 person1.proto === Person.prototype

第二题:

因为 Person.proto === Person 的构造函数.prototype

所以 Person.proto === Function.prototype

第三题:

Person.prototype 是一个普通对象,我们无需关注它有哪些属性,只要记住它是一个普通对象。

因为一个普通对象的构造函数是 Object

所以 Person.prototype.proto === Object.prototype

第四题,参照第二题,因为 Person 和 Object 一样都是构造函数

Object.proto === Function.prototype

第五题:

Object.prototype 对象也有 proto 属性,但它比较特殊,为 null 。因为 null 处于原型链的顶端,这个只能记住。Object.prototype.proto === null

继承

javascript 中的继承是通过原型链来体现的。先看几句代码

访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着 proto 这条链向上找,这就是原型链。

访问 f1.b 时,f1 的基本属性中没有 b,于是沿着 proto 找到了 Foo.prototype.b。

 如何区分一个属性到底是自己的还是从原型中找到的呢?使用 hasOwnProperty,特别是在 for…in… 循环中。

 

f1 的这个 hasOwnProperty 方法是从哪里来的? f1本身没有,Foo.prototype 中也没有

它是从 Object.prototype 中来的

对象的原型链是沿着 proto 这条线走的,因此在查找 f1.hasOwnProperty 属性时,就会顺着原型链一直查找到 Object.prototype。

 

由于所有的对象的原型链都会找到 Object.prototype,因此所有的对象都会有 Object.prototype 的方法。这就是所谓的“继承”。

说一个函数的例子吧。

我们都知道每个函数都有 call,apply方法,都有 length,arguments,caller 等属性。为什么每个函数都有?这肯定是“继承”的。函数由 Function 函数创建,因此继承的 Function.prototype 中的方法。不信可以请微软的 Visual Studio 老师给我们验证一下:

那怎么还有 hasOwnProperty 呢?—— 那是 Function.prototype 继承自 Object.prototype 的方法。

参考

深入理解原型和闭包

一文吃透所有原型相关知识

深度解析原型中的各个难点