JavaScript构造函数和原型原型链及this指向

Posted 遥岑.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript构造函数和原型原型链及this指向相关的知识,希望对你有一定的参考价值。

构造函数和原型

构造函数原型prototype

构造函数通过原型分配的函数是所有对象所共享的,不用单独开辟内存空间。
javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例都可以共享这些方法。

ES5:面向对象通过构造函数来实现

function Animal(name)
{
    this.name = name
    this.run = function()
    {
        console.log(this.name + '会跑')
    }
}

var cat = new Animal('猫咪')
cat.run()
var dog = new Animal('狗狗')
dog.run()
console.log(cat.run === dog.run)  //所指向的run函数不一样,是两个不用的内存地址

//输出结果:
//猫咪会跑
//狗狗会跑
//false



//改用原型对象
function Animal(name)
{
    this.name = name
}
Animal.prototype.run = function()  //原型对象添加方法
{
    console.log(this.name + '会跑')
}

var cat = new Animal('猫咪')
cat.run()
var dog = new Animal('狗狗')
dog.run()
console.log(cat.run === dog.run)  //共享方法

//输出结果:
//猫咪会跑
//狗狗会跑
//true
  • 一般情况下,我们的公共属性定义在构造函数里面,公共的方法放在原型对象身上

如果方法放在构造函数里面,创建实例时都会为这个方法单独再开辟一块内存空间来存放同一个函数,浪费内存
解决方法:公共方法放在原型对象身上,这样所有实例可以共享方法

对象原型__proto__

对象都会有一个属性__proto__指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象由__proto__原型的存在。
__proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象prototype。

方法的查找规则:

  1. 首先先看对象身上是否有这个方法,如果有就执行对象上的方法
  2. 如果没有,因为有__proto__的存在,就去构造函数原型对象prototype身上去查找这个方法
function Animal(name)
{
    this.name = name
}
Animal.prototype.run = function()  //原型对象添加方法
{
    console.log(this.name + '会跑')
}

console.log(cat.__proto__ === Animal.prototype)  //是等价的

//输出结果: true
  • 对象身上系统自动添加一个__proto__,指向我们构造函数的原型对象prototype
  • 对象原型__proto__和原型对象prototype是等价的

构造函数constructor

对象原型(proto)和构造函数原型对象(prototype)里面都有一个constructor属性, constructor我们称为构造函数,因为它指回构造函数本身。
constructor主要用于记录该对象引用于哪个构造函数,由哪个构造函数创造出来的,它可以让原型对象重新指向原来的构造函数。

function Animal(name)
{
    this.name = name
}
Animal.prototype.run = function()  //原型对象添加方法
{
    console.log(this.name + '会跑')
}

//都指向的是Animal这个构造函数
console.log(Animal.prototype.constructor)  
console.log(cat.__proto__.constructor)

很多时候,我们需要手动的利用constructor这个属性指回原来的构造函数。

function Animal(name)
{
    this.name = name
}
//给这个对象添加多个方法
//我们修改了原型对象,给原型对象赋值的是一个对象
Animal.prototype = {
    //手动使用constructor指回原来的构造函数
    constructor:Animal,
    run:function(){
        console.log(this.name + '会跑')
    },
    say:function(){
        console.log(this.name + '会叫')
    }
}
var cat = new Animal('猫咪')
cat.run()
cat.say()
var dog = new Animal('狗狗')
dog.run()
dog.say()
//如果不添加上面的constructor 原型对象被覆盖 无法返回构造函数
console.log(Animal.prototype.constructor)
console.log(cat.__proto__.constructor)

构造函数、实例、原型对象三者的关系

关系图

原型链

只要是对象就有__proto__原型,指向原型对象。

function Animal(name)
{
    this.name = name
}
Animal.prototype.run = function()  //原型对象添加方法
{
    console.log(this.name + '会跑')
}

console.log(Animal.prototype.__proto__)

//Animal原型对象里面的__proto__原型指向的是Object.prototype
console.log(Animal.prototype.__proto__ === Object.prototype)
//输出结果: true

//Object.prototype原型对象里面的__proto__原型,指向为null
console.log(Object.prototype.__proto__)
//输出结果: null

原型链

JavaScript中成员查找机制

按原型链的方式,一层一层往上查找。
__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
若出现冲突,就近原则

访问顺序:

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象本身有没有该属性
  2. 如果没有就查找它的原型(也就是__proto__指向的prototype原型对象)
  3. 如果还没有就查找原型对象的原型(Object的原型对象)
  4. 以此类推一直找到Object的原型对象为止(null)
function Animal(name)
{
    this.name = name
    //第一层查找 对象里
    //this.age = '大猫'
}
Animal.prototype.run = function()  //原型对象添加方法
{
    console.log(this.name + '会跑')
}

var cat = new Animal('猫咪')

//若没有第一层 第二层查找 它的原型
//Animal.prototype.age = '幼崽'

//没有 第三层查找 Object原型
//Object.prototype.age = '大猫'

//Object原型有没有 返回undefined
console.log(cat.age)

this关键字

this的指向

  • 构造函数内部的this指向新创建的对象,不代表这个类,指的是由类指针创建出来的对象
  • 直接通过函数名调用函数时,this指向的是全局变量window(默认)
  • 如果将函数作为对象的方法调用,this会指向该对象

更改this指向

  1. apply( )方法
function method(a,b)
{
    console.log(a+b)
}

method.apply({},['1','2'])  //传参:对象、数组

//输出结果: 12
  • 数组方式传参
  • apply(obj,argArray)
  1. call( )方法
function method(a,b)
{
    console.log(a+b)
}

method.call({},'3','4') //第一个传入的是对象

//输出结果: 34
  • 参数方式传参
  • call(obj,arg1,arg2,…)
  1. blind( )方法
    实现提前绑定的效果,在绑定时,可以提前传入调用函数时的参数。
function method(a,b)
{
    console.log(this.name+a+b)
}

var name = '遥岑'
method('1','2')    //全局变量name
//输出结果  遥岑12

var test = method.bind({name:'孟时蓝'},'3','4') //绑定对象
test() 
//输出结果  孟时蓝34

错误处理

try-catch语法:

try
{
	 可能会出现错误的代码
}
catch(e)  //代码的错误类型
{  
	 错误出现后的处理代码
}
  • 如果代码出现错误,后面的代码将不会被执行

throw抛出错误对象:

try
{
    var e1 = new Error('错误信息')  //创建错误对象e1
    throw e1   //抛出错误对象
}
catch(e) //e接收错误对象
{
    console.log(e.message)   //'e'是用来接收try里面抛出的错误对象 打印错误对象的信息
    console.log(e===e1)      //所指向的存储空间是一样的
}

错误类型:

类型说明
Error普通错误,其余6种类型的错误对象都继承自该对象
EvalError调用eval( )函数错误,已经弃用,为了向后兼容,低版本还可以使用
RangeError数值超出有效范围,如 new Array(-1)
ReferenceError引用了一个不存在的变量,如 var a = 1; a + b(变量b未定义)
SyntaxError解析过程语法错误,如"{ ; } , if( ) ,var a = new
TypeError变量或参数不是预期类型,如调用了不存在的函数或方法
URIError解析URI编码出错,调用encodeURI( )、escape( )等URI处理函数时出现

继承

ES6之前并没有提供 extends 继承。可以通过 构造函数+原型对象 模拟实现继承,被称为组合继承。

借用构造函数继承父类属性

ES5:
call( ):将父类的this指向子类的this,实现子类继承父类的属性

//es5里面没有继承extends 这样写继承
function Father(uname,age)
{
    this.uname = uname
    this.age = age
}

function Son(uname,age,score)
{
    Father.call(this,uname,age)  //改变this的指向
    this.score = score
}

var son = new Son('遥岑',19,100)
console.log(son)

ES6:

// 使用super继承(ES6)
class Father 
{
    constructor(name, age) 
    {
        this.name = name
        this.age = age
    }
}

class Son extends Father 
{
    constructor(name, age, score) 
    {
        super(name, age)  //继承父类构造函数
        this.score = score
    }
}
var son = new Son('遥岑',19,100)
console.log(son)

利用原型对象继承父类方法

一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。
原型对象继承父类方法:将父类的实例对象作为子类的原型对象来使用。

function Father() { }
Father.prototype.say = function()
{
    console.log('我是父亲')
}

function Son() { }
//Son.prototype = Father.prototype   //这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
Son.prototype = new Father()  //将父亲的实例对象作为子类的原型对象
//如果利用对象的形式修改了原型对象,别忘了利用constructor指回原来的原型对象
Son.prototype.constructor = Son
var s = new Son()
s.say()
//输出结果:我是父亲
  • 将子类所共享的方法提取出来,让子类的prototype原型对象 = new 父类( )
  • 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
  • 将子类的constructor重新指向子类的构造函数

原型链

类的本质

ES6之前通过构造函数+原型实现面向对象编程。
ES6通过实现面向对象编程。

class本质还是function,也可以简单认为类就是构造函数的另一种写法。

构造函数的特征:

  • 构造函数有原型对象prototype
  • 构造函数原型对象prototype里面有constructor指向构造函数本身
  • 构造函数可以通过原型对象添加方法
  • 构造函数创建的实例对象由__proto__原型指向构造函数的原型对象

类的所有方法都定义在类的prototype属性上

  • 类创建的实例,里面也有proto指向类的prototype原型对象
  • 类可以通过原型对象来添加方法
  • 所以ES6的类它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已
  • 所以ES6的类其实就是语法糖

语法糖:语法糖就是一种便捷写法。
简单理解, 有两种方法可以实现同样的功能, 但是一种写法更加清晰、方便,那么这个方法就是语法糖

以上是关于JavaScript构造函数和原型原型链及this指向的主要内容,如果未能解决你的问题,请参考以下文章

原型及原型链及作用域链

Js中的对象构造函数原型原型链及继承

Js中的对象构造函数原型原型链及继承

JavaScript构造函数,原型对象原型链,this指向,错误处理

JavaScript 构造函数和原型

JavaScript-构造函数和原型