前言
上篇文章中,在分析bind手写的时候,涉及到了原型和原型链的知识。于是就将原型和原型链的一些知识进行总结整理。
首先我们先去思考一个问题,在上篇提到的手写call,apply,bind方法的时候,是将其挂载在Function对象的prototype属性上的。这样,所有的方法都可以直接去使用我们挂载的方法。but why???
JS中的函数和对象
首先介绍下几个基本概念
- 函数(function)
- 函数对象(function object)
- 本地对象(native object)
- 内置对象(build-in object)
- 宿主对象(host object)
函数
以下都是函数。前者是函数声明,后者是函数表达式。
function talk(){}
const say = function(){}
typeof talk //function
typeof say //function
函数对象
JS中一切皆对象。代表函数的对象就是函数对象。
官方定义, 在Javascript中,每一个函数实际上都是一个函数对象.
JavaScript代码中定义函数,或者调用Function创建函数时,最终都会以类似这样的形式调用Function函数:var newFun = new Function(funArgs, funBody) 其实也就是说,我们定义的函数,语法上,都称为函数对象,看我们如何去使用。如果我们单纯的把它当成一个函数使用,那么它就是函数,如果我们通过他来实例化出对象来使用,那么它就可以当成一个函数对象来使用,在面向对象的范畴里面,函数对象类似于类的概念。
const talk = new function(){}
typeof talk //Object
function Talk(){}
const talk = new Talk()
typeod talk //Object
本地对象
ECMA-262 把本地对象(native object)定义为“独立于宿主环境的 ECMAScript 实现提供的对象”。简单来说,本地对象就是 ECMA-262 定义的类(引用类型)。它们包括:Object,Function,Array,String,Boolean,Number,Date,RegExp,Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError.
typeof(Object)
typeof(Array)
typeof(Date)
typeof(RegExp)
//全部都是 function
返回的结果都是function。这也就说明,这些内置对象都是通过构造函数的方式创建的。
function Array(){}
function Date(){}
function Vue(){}
内置对象
ECMA-262 把内置对象(built-in object)定义为“由 ECMAScript 实现提供的、独立于宿主环境的所有对象,在 ECMAScript 程序开始执行时出现”。这意味着开发者不必明确实例化内置对象,它已被实例化了。ECMA-262 只定义了两个内置对象,即 Global 和 Math (它们也是本地对象,根据定义,每个内置对象都是本地对象)。
构造函数
在ES6引入类(class)的概念之前,js是通过构造函数来创建实例的。 构造函数是通过new运算符创建并初始化一个新对象,关键字new后面跟随的是一个函数调用,这个函数被称为构造函数。构造函数用来初始化一个新创建的对象。
function Student(name){
this.name = name
this.grade = 1 //设置一个实例的共有属性,grade
}
//生成学生实例对象
const student1 = new Student('zhangsan')
const student2 = new Student('lisi')
这两个学生属性的年级信息是独立的,修改其中一个不会影响到另外一个。
student1.grade = 2
console.log(student2.gradder) //1
从上面的例子可以看出,每一个构造函数的实例都有自己的属性和方法的副本,如果需要统一更改某个属性的话,将会是无尽的麻烦,我们期望的是,通过改变构造函数中的某个属性,使其实例对象上的属性都一起更改。所以,为了解决数据无法共享的问题,引入了prototype属性。prototype属性是一个指针,指向一个对象,这个对象中包含了所有实例共享的属性和方法。也就是说,prototype是通过调用构造函数(new 关键字创建)创建的那个实例对象的原型对象。因为我们可以将需要共享属性和方法都放到这个对象中。
function Student(name){
this.name = name
}
Student.prototype = {
grader:1 //设置公共属性
}
//生成学生实例对象
const student1 = new Student('zhangsan')
const student2 = new Student('lisi')
//修改公共属性
Student.prototype.grade = 4
console.log(student1.prototype) //4
console.log(student2.prototype) //4
__proto__
__proto__和constructor属性是对象所独有的
prototype属性是函数所独有的
__proto__是对象独有的。__proto__属性都是由一个对象指向另外一个对象,即指向他们的原型对象。它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的
__proto__属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往父对象的__proto__属性所指向的那个对象(可以理解为爷爷对象)里找,如果还没找到,则继续往上找…直到原型链顶端null(可以理解为原始人。。。),再往上找就相当于在null上取值,会报错(可以理解为,再往上就已经不是“人”的范畴了,找不到了,到此结束,null为原型链的终点),由以上这种通过__proto__属性来连接对象直到null的一条链即为我们所谓的原型链。
们平时调用的字符串方法、数组方法、对象方法、函数方法等都是靠__proto__继承而来的。
prototype
prototype属性是函数独有的。它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象。
它的作用就是包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们都可以找到公用的属性和方法。任何函数在创建的时候,其实会默认同时创建该函数的prototype对象。
constructor
constructor属性也是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数,每个对象都有构造函数。,从上图中可以看出Function这个对象比较特殊,它的构造函数就是它自己(因为Function可以看成是一个函数,也可以是一个对象),所有函数和对象最终都是由Function构造函数得来,所以constructor属性的终点就是Function这个函数。
每个对象都可以找到其对应的constructor,因为创建对象的前提是需要有constructor,而这个constructor可能是对象自己本身显式定义的或者通过__proto__在原型链中找到的。而单从constructor这个属性来讲,只有prototype对象才有。每个函数在创建的时候,JS会同时创建一个该函数对应的prototype对象,而函数创建的对象.proto === 该函数.prototype,该函数.prototype.constructor===该函数本身,故通过函数创建的对象即使自己没有constructor属性,它也能通过__proto__找到对应的constructor,所以任何对象最终都可以找到其构造函数(null如果当成对象的话,将null除外)
总结
1、__proto__和constructor属性是对象所独有的
2、prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__和constructor属性
3、__proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,再往上找就相当于在null上取值,会报错。
4、prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.__proto__ === Foo.prototype
5、constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function