前端面试深入理解系列(一)——__proto__,prototype原型以及原型链

394 阅读6分钟

今天这节可能有点长,希望大家认真看,收获会很多

1. __proto__由来

我们都知道javascript是一种面向对象的语言,面向对象语言的一大特点就是一切皆对象,我们常见的一些数字,数组,字符串等都是对象。但是虽然都是对象,但因为表现形式不一样又分为普通对象和函数对象。

普通对象

  var obj={
    name:"xiaoming"
  };

输出以后

1111

我们可以看到"proto"里面有很多的方法,这些方法都是可以直接使用的

函数对象

 function Test() {}
 console.log(Test);

输出以后

image.png

可以看到既有prototype属性,也有__proto__属性

从上面的例子我们可以看出普通对象和函数对象的定义和输出后的样子

不管是普通对象还是函数对象,它们都有一个隐含属性__proto__ ,而这属性就是我们通常说的原型(属性),这就是__proto__的由来

重点每一个对象都有__proto__属性,指向对应的构造函数的prototype属性

怎么理解好一点呢? 我们先来创建一个数组,输出他的__proto__属性

let arr1=Array.of(1,2,3);
 console.log(arr1);

4444 接着我们输出数组构造函数的prototype

console.log(Array.prototype);

5555 结论就是发现他们是相等的

2.prototype的由来

对于函数对象,它们还会多一个prototype的属性,它和以它为构造函数创建的普通对象的”proto “属性等同。

即实例对象的__proto__指向它的构造函数prototype(划重点)(这也就是为什么实例对象能够使用原型对象上面方法的原因)

es5写法  
function Person(name,age){
	this.name=name;
	this.age=age;
}
var xiaoming=new Person("小明",10);
console.log(xiaoming.__proto__==Person.prototype);
es6写法
class Person {
    constructor(name,age) {
        this.name = name;
        this.age = age;
    }

    toString(){
        return this.name+'今年'+this.age+'岁!'
    }
}

const xiaoming=new Person('小明',10);
console.log(xiaoming.toString());
console.log(typeof Person);
// 函数对象的是prototype,普通对象的是__proto__
// 每一个对象都有_proto_属性,指向对应的构造函数的prototype属性,也可以说实例的__proto__指向对应的类对象的prototype

console.log(xiaoming.__proto__===Person.prototype);
// 很多人会问,为什么prototype这么重要呢,接下来我们会讲原型链,告诉你,那些最原始的方法都是怎么来的

下面讲一下prototype的作用

javascript是通过构造函数来实现实例对象的,实例对象的属性和方法都是在构造函数内部来定义的

<script>
		function Person(name,age){
			this.name=name;
			this.age=age;
		}
		var xiaoming=new Person("xiaoming",10);
		console.log(xiaoming.name);
		console.log(xiaoming.age);
	</script>

这样写的缺点是但是同一个构造函数之间无法共享方法和属性,造成了系统资源的浪费,比如说你创建两个Person实例,他们都有一个共同的方法比如说睡觉,可是每生成一个实例就会重新生成一个睡觉的方法。所以这就有了prototype.

**JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。**这里所说的原型也就是prototype了,这样子就能节省内存了

这句话的理解就是我们把私有的属性和方法定义在构造函数里面,把公有的属性定义在prototype上

3.__proto__和prototype的关系

一.所有构造器/函数的__proto__都指向Function.prototype,它是一个空函数(Empty function)

Number.__proto__ === Function.prototype  // true
Boolean.__proto__ === Function.prototype // true
String.__proto__ === Function.prototype  // true
Object.__proto__ === Function.prototype  // true
Function.__proto__ === Function.prototype // true 
Array.__proto__ === Function.prototype   // true
RegExp.__proto__ === Function.prototype  // true
Error.__proto__ === Function.prototype   // true
Date.__proto__ === Function.prototype    // true

这里所说的所有当然也包括自定义的函数

var Dog = function(){};
console.log(Dog.__proto__==Function.prototype);

我们知道prototype是放置公有属性和方法的地方,那就是说我们自定义的Dog函数原型继承了Function函数的原型,比如说bind,call,apply,length方法

Function.prototype也是唯一一个typeof XXX.prototype为 “function”的prototype。其它的构造器的prototype都是一个对象 如下

console.log(typeof Function.prototype) // function
console.log(typeof Object.prototype)   // object
console.log(typeof Number.prototype)   // object
console.log(typeof Boolean.prototype)  // object
console.log(typeof String.prototype)   // object
console.log(typeof Array.prototype)    // object
console.log(typeof RegExp.prototype)   // object
console.log(typeof Error.prototype)    // object
console.log(typeof Date.prototype)     // object
console.log(typeof Object.prototype)   // object

上面我们提到Function.prototype是一个空函数, 输出后为 image.png

console.log(Function.prototype.__proto__ === Object.prototype) // true

那么也就是说构造器和函数都会继承Object.prototype上的方法和属性 image.png

最后Object.prototype的__proto__是谁?

Object.prototype.__proto__ === null  // true

也就是说通过__proto__的最顶端是null。

二、所有对象的__proto__都指向其构造器的prototype(看一下new源码就明白了) 先看看javascript内置构造器

var obj = {name: 'jack'}
var arr = [1,2,3]
var reg = /hello/g
var date = new Date
var err = new Error('exception')
 
console.log(obj.__proto__ === Object.prototype) // true
console.log(arr.__proto__ === Array.prototype)  // true
console.log(reg.__proto__ === RegExp.prototype) // true
console.log(date.__proto__ === Date.prototype)  // true
console.log(err.__proto__ === Error.prototype)  // true

再看看自定义的构造器

function Person(name) {
    this.name = name
}

var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true

每个对象都有一个constructor属性,可以获取它的构造器

function Person(name) {
    this.name = name
}

var p = new Person('jack')
console.log(p.__proto__ === p.constructor.prototype) // true

需要注意的是

function Person(name) {
    this.name = name
}

// 重写原型
Person.prototype = {
    getName: function() {}
}

var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false

这种情况是因为给Person.prototype赋值的是一个对象直接量{getName: function(){}},使用对象直接量方式定义的对象其构造器(constructor)指向的是根构造器 Object,Object.prototype是一个空对象{},{}自然与{getName: function(){}}不等

var p = {}
console.log(Object.prototype) // 为一个空的对象{}
console.log(p.constructor === Object) // 对象直接量方式定义的对象其constructor为Object
console.log(p.constructor.prototype === Object.prototype) // 为true,不解释 

4.原型链

从上面的知识我们可以知道对象的__proto__指向其构造函数的prototype,而构造函数的prototype指向Function.prototype,Function.prototype又指向Object.prototype,从而继承Object上面的方法,就这样对象到原型再到原型的原型,这种链式结构我们称之为原型链

从所有构造器/函数的proto都指向Function.prototype那一章我们可以知道,原型链的顶层是null

那么对象是如何获取属性和方法的呢,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。

function Person(name,age){
	this.name=name;
	this.age=age;
}
Person.prototype.color='white';
var xiaoming=new Person("小明",10);
xiaoming.color='yello';
var xiaoli=new Person("小李",12);
console.log(xiaoming.color);
console.log(xiaoli.color);

从上面我们还可以得出结论,如果原型指向数组,那么我们就可以拥有数组上方法

var MyArray = function () {};

MyArray.prototype = new Array();
MyArray.prototype.constructor = MyArray;

var mine = new MyArray();
mine.push(1, 2, 3);
mine.length // 3
mine instanceof Array // true

5.总结:

1.为什么每个对象都有toString,length,name等方法?

答:每一个对象都有__proto__属性,指向对应的构造函数的prototype属性,所以这个对象能够使用其构造函数prototype中的方法。在答一下原型链

2.什么是原型链?

答:对象的__proto__指向其构造函数的prototype,而构造函数的prototype指向Function.prototype,Function.prototype又指向Object.prototype,从而继承Object上面的方法,就这样对象到原型再到原型的原型,这种链式结构我们称之为原型链

3.__proto__和prototype区别是什么?

答:所有的对象都有__proto__属性,所有的函数对象都有prototype属性

4.设计原型的初衷或者背景是什么?

答:1)javascript采用原型编程,所有的对象都能共享原型上的方法和属性,这样可以节省内存,如果不使用原型就会造成每创建一个对象就会产生一个内存地址(没有两个单独的对象,而是有两个变量都引用同一对象。引用不占用对象的空间,它们只是内存指针) 2)方便实现继承

参考:wangdoc.com/javascript/…

blog.csdn.net/hkh_1012/ar…