原型与原型链

641 阅读7分钟

说原型之前,我们先来认识一下函数。

函数

函数:是被设计为执行特定任务的代码块。

  • 系统函数(JavaScript自带的函数,如alert)
  • 自定义函数(开发自己编写的函数)

函数声明

  • 通过function关键字调用,声明后,不会立即执行,只有在调用的时候执行。
// 声明一个函数
function getName(){
    console.log('1')
}
// 调用
getName()
  • 通过表达式定义,函数存储在变量中,使用时,变量可以作为一个函数。
let getName = function(){
    console.log('1')
}

getName()

匿名函数:

常用方式

//第一种 通过表达式定义的匿名函数
var a=function(){}
//第二种 通过将函数作为参数,定义的函数为匿名函数
addNumber(function(){})
//第三种 对象中的函数
var obj={
 say:function(){}
}

匿名自执行函数

匿名自执行函数首先是一个匿名函数,但是这个函数是可以自己自动执行的,不需要借助其他的元素

  • 闭包中常用
  • 模拟创建块级作用域,减少全局变量的数量
  • 匿名自执行函数执行结束,变量就会被内存释放掉
//第一种
(function(data){
  console.log(data)
})('传参')

//第二种
(function(data){
  console.log(data)
}())

//第三种
!(function(data){
  console.log(data)
})('传参')

//第四种 
var fun=function(data){
  console.log(data)
}('传参')

构造函数

构造函数也是一个普通函数,创建方法和普通函数一样

构造函数和普通函数的区别:

1.命名,构造函数习惯大写,普通函数驼峰命名

function Person(){}
function person(){}

2.调用方式,普通函数直接调用,构造函数需要new关键字来创建一个实例对象

function Person(){}
function person(){}

person();
var per=new Person()
per();

3.构造函数内部的this指向新建的Person实例,普通函数内部的this指向调用函数的对象(如果没有调用对象,则默认为window)

4.构造函数通过new来创建实例的执行流程

  • 在堆内存中创建一个新的对象
  • 将新建对象设置为函数中的this
  • 逐个执行函数中的代码
  • 将新建对象作为返回值
function Person(name, age, job) {
 this.name = name;
 this.age = age;
 this.job = job;
 this.sayName = function() { alert(this.name) } 
}
var person1 = new Person('Zaxlct', 28, 'Software Engineer');
var person2 = new Person('Mick', 23, 'Doctor');

实例的构造函数属性(constructor)指向构造函数。即:

console.log(person1.constructor == Person); //true
console.log(person2.constructor == Person); //true

对象

我们再来了解一下对象。

①本地对象:独立于宿主环境(浏览器)的对象——包括Object、Array、Date、RegExp、Function、Error、Number、String、Boolean

②内置对象 ——包括Math、Global(window,在js中就是全局变量),使用的时候不需要new

③宿主对象 ——包括自定义对象、DOM、BOM

函数对象和普通对象

javascript中,万物皆对象

对象也有区别:分为普通对象和函数对象

var o1 = {}; 
var o2 =new Object();
var o3 = new f1();

function f1(){}; 
var f2 = function(){};
var f3 = new Function('str','console.log(str)');

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

console.log(typeof f1); //function 
console.log(typeof f2); //function 
console.log(typeof f3); //function   

console.log(typeof o1); //object 
console.log(typeof o2); //object 
console.log(typeof o3); //object

在上面的例子中 o1 o2 o3 为普通对象,f1 f2 f3 为函数对象。

凡是通过 new Function()创建的对象都是函数对象,其他的都是普通对象。

原型

1. 什么是原型?

简单来讲,xxxx.prototype就是原型。

2. 什么是原型对象?

拥有prototype属性的对象。

3. 什么样的对象拥有prototype属性?

我们来定一个其他类型的变量。


let num  = new Number(1)
console.log(num.prototype) // undefined

let num  = {}
console.log(num.prototype) // undefined

从上可以看出,除了函数,其他类型都没有这个属性。

可以说,prototype是函数的一个属性。

在定义函数时会创建一个函数对象,所有函数对象都拥有prototype属性,我们称为显式原型。

function getName(){}
// 或者 let getName = function(){}
console.log(getName.prototype)

浏览器中打印,可以看到,默认情况下,prototype拥有两个属性。

第一个是constructor,指向原型对象所属的构造函数

第二个是__proto__,指向构造该对象的构造函数的原型

需要注意的是,箭头函数,是没有prototype的。

let fn = ()=>{}
console.log(fn.prototype)// undefined

4. __proto__是什么?

它不包括在js语言规范中,但浏览器中都实现了它。

它是对象的一个属性,所有的对象都拥有__proto__属性,我们称作隐式原型。

它指向的是:构造该对象的构造函数的原型。

这句话有点拗口。

来看一个例子。

首先定义一个字符串对象,打印出这个对象的__proto__,我们知道,字符串的构造函数是String(),再打印出构造函数String()的原型。

let str = '123'
console.log(str.__proto__)
console.log(String.prototype)
// 两者是一样的,打印出一长串属性,不多说。

我们再来读一下这句话:__proto__指向构造该对象的构造函数的原型。

所以,如下等式成立:

str.__proto__ === String.prototype // true
  • 什么是实例?

通过构造函数生成的对象。

function Person(){}

let p = new Person()
let p2 = new Person()

p,p2都是构造函数Person一个的实例。

原型链

原型链的核心就是依赖对象的_proto_的指向,当自身不存在的一个属性时,就一层层的扒出创建对象的构造函数,直至到Object时,就没有__proto__指向了。

普通对象的原型链

我们依然还是从字符串说起。

let str = '123'

从上我们知道:

str.__proto__ === String.prototype

js中,万物皆对象,String.prototype也是个对象的,那么String.prototype对象也有__proto__属性。String.prototype.__proto__指向构造这个对象的构造函数的原型。

String.prototype是怎么创建的?

我们知道,String()是个构造函数,函数对象的prototype属性在函数定义的时候就已经创建了,对象的创建,通过Object构造函数。

所以有了如下等式:

String.prototype.__proto__ === Object.prototype // true

// 因为
str.__proto__ === String.prototype // true

// 所以
str.__proto__.__proto__ = String.prototype.__proto__ // true
str.__proto__.__proto__ = Object.prototype // true

那么,问题又来了,Object.prototype对象的__proto__属性是什么?

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

可以看出,Object.prototype往下,就没有__proto__属性了。

而一条原型链就已经形成。

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

比如,我们调用一个方法getAge()

str.getAge()

这个方法,在str的属性上,是没有的

沿着原型链往上找,它的构造函数的原型String.prototype也没有

继续往上找,它的构造函数的原型上的构造函数的原型Object.prototype也不存在这个方法。

继续往上找,只有null了。

那么浏览器就会报错,提示这个方法未定义不存在。

原型链的最顶端,就是null

函数对象的原型链

函数对象比普通对象难的是,构造函数可以通过原型链继承另一个构造函数的原型及其所有属性和方法。

非原型链继承

简单的构造函数,不继承任何。它的原型链如下:

function Person(){}
let p1 = new Person()

p1是实例对象,拥有__proto__属性,指向它的构造函数的原型。

p1.__proto__ === Person.prototype // true

Person.prototype.__proto__ === Object.prototype
p1.__proto__.__proto__ === Object.prototype

Object.prototype.__proto__ === null
p1.__proto__.__proto__.__proto__ === null

原型链继承

创建一个Dad构造函数,Son构造函数,并且通过原型链,让Son继承Dad。

function Dad(age){
    this.age = age
}
Dad.prototype.getAge = function(){
    return age
}
function Son(name){
    this.name = name
}
let dad_ex1 = new Dad()
Son.prototype = dad_ex1 // 继承 Son
let son_ex1 = new Son()

这种原型链继承的情况下,原型链如下:

// 子类实例的隐式原型 指向Son构造函数的原型
son_ex1.__proto__ === Son.prototype // true

// 因为继承
Son.prototype = dad_ex1

//所以 Son构造函数的原型的隐式原型 等同于 父类实例的隐式原型
Son.prototype.__proto__ === dad_ex1.__proto__  // true

// 因为 父类实例的隐式原型 指向父类Dad构造函数的原型
dad_ex1.__proto__ === Dad.prototype //true

// 所以
Son.prototype.__proto__ === Dad.prototype //true
son_ex1.__proto__.__proto__ === Dad.prototype //true

// 所以
Son.prototype.__proto__.__proto__ === Dad.prototype.__proto__ // true
son_ex1.__proto__.__proto__.__proto__ === Dad.prototype.__proto__ // true

// 因为 父类原型的隐式原型 指向 Object构造函数的原型
Dad.prototype.__proto__ === Object.prototype // true

// 所以
Son.prototype.__proto__.__proto__ === Object.prototype //true
son_ex1.__proto__.__proto__.__proto__ === Object.prototype //true

// 所以
Son.prototype.__proto__.__proto__.__proto__ === Object.prototype.__proto__ //true
son_ex1.__proto__.__proto__.__proto__.__proto__ === Object.prototype.__proto__ //true

// 因为 Object原型的隐式原型 指向 null
Object.prototype.__proto__ === null // true

// 所以
Dad.prototype.__proto__.__proto__ === null
Son.prototype.__proto__.__proto__.__proto__ === null //true
son_ex1.__proto__.__proto__.__proto__.__proto__ === null //true

说到最后,如果构造函数Dad继承另一个构造函数,另一个构造函数继承另另一个构造函数,另另一个构函数继承另另另一个构造函数……如此继承下去。

那就是一条漫长的原型链了。

原型链就说到这里,如果我理解得不对,欢迎指正。