javascript深入理解系列文章网址
https://www.jianshu.com/p/451eed9094f5
今天这节可能有点长,希望大家认真看,收获会很多
(2020年1月2日更新,添加es6写法)
1._proto_由来
我们都知道javascript是一种面向对象的语言,面向对象语言的一大特点就是一切皆对象,我们常见的一些数字,数组,字符串等都是对象。但是虽然都是对象,但因为表现形式不一样又分为普通对象和函数对象。
普通对象
var obj={
name:"xiaoming"
};
输出以后
我们可以看到"_proto_"里面有很多的方法,这些方法都是可以直接使用的
函数对象
function Person(){
alert("你好");
}
输出以后
从上面的例子我们可以看出普通对象和函数对象的定义和输出后的样子
我们再看一下typeof后普通对象和函数对象的区别
console.log(typeof obj);
console.log(typeof(Person));
不管是普通对象还是函数对象,它们都有一个隐含属性_proto_ ,而这属性就是我们通常说的原型(属性),这就是_proto_的由来
重点每一个对象都有_proto_属性,指向对应的构造函数的prototype属性
怎么理解好一点呢?
我们先来创建一个数组,输出他的\__proto__属性
let arr1=Array.of(1,2,3);
console.log(arr1);
接着我们输出数组构造函数的prototype
console.log(Array.prototype);
结论就是发现他们是相等的
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是一个空函数,
输出后为
console.log(Function.prototype.__proto__ === Object.prototype) // true
那么也就是说构造器和函数都会继承Object.prototype上的方法和属性
最后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