JavaScript-面向对象编程-学习笔记

373 阅读6分钟

引言

为了准备暑假的实习,特地复习js相关知识,个人学习笔记,如有错误希望各位指出

典型的面向对象语言(比如java),都有类(class)的概念,但是JavaScript的面向对象,不是基于类实现的,而是基于构造函数(constructor)和原型链(prototype)实现的

JavaScript使用构造函数作为对象的模板,构造函数就是一个普通的函数

var Person =function(){
    this.name ='yzh'
}

但有自己的特征:

  • 函数体内使用this关键字(代表所要生成的对象实例)
  • 生成对象时,使用new命令

new命令

new命令的作用,就是执行构造函数,返回一个实例对象

var Person =function(){
    this.name ='yzh'
}

var person1 =new Person()
person1.name  //yzh

前面说过,构造函数其实就是一个普通的函数,如果直接执行会怎样呢?

var person1 =Person()
person1 //undefined
name //yzh

为什么会出现这种情况呢? 当作为普通函数调用时,this指向全局对象,相当于我们调用函数创建了一个全局变量name, 可见JavaScript中面向对象实现的关键在于new关键字

new命令的原理

使用new命令时,具体干了什么呢?

  • 创建一个空对象,作为将要返回的实例
  • 将该空对象的原型,指向构造函数的prototype属性
  • 将这个空对象赋值给函数内部的this关键字
  • 开始执行构造函数内部的代码
function _new (constructor,params) {
     //创建一个空对象
     var obj ={}
     //让空对象的原型指向构造函数的prototype属性
     obj.__proto__ = constructor.prototype
     //执行构造函数
     constructor.apply(obj,params)
     //返回obj
     return obj
}

其中,constructor是构造函数,params是构造函数的参数

当然,这里的new函数仅实现了大致功能,具体的new函数还会考虑构造函数是否本身就返回了一个对象等其他功能点

看到这里,可能有些读者不明白什么是__proto__,prototype

前面说了,JavaScript面向对象的实现依据构造函数和原型链,构造函数说过了,下面谈一谈原型链

原型是什么

在js中每个函数都有一个prototype属性,他实际指向的是一个原型对象

控制台上输出可见:

image.png

可见Person.prototype就是一个对象,包含constructor和__proto__两个属性

每个js对象(除了null)都具有的一个属性,就是__proto__,该属性指向该对象的原型

image.png

可以初步得出以下结论: Person.prototype =p1.__proto__

接下来我们看看Person.prototype.__proto__是什么??

image.png

前面说到,Person.prototype就是一个对象,所以就是Object这个构造函数的一个实例,那么Person.prototype.__proto__其实就是Object.prototype

原型链是什么?

上面说到,Person.prototype.__proto__ 就是Object.prototype

我们也看到了,Object.prototype还有一个__proto__属性,它又是什么呢?

image.png

可见Object.prototype.__proto__ ==null

所谓原型链,就是obj.__proto__.__proto__.__proto__........直到null

总结

讲了上面这么多概念,说实话头多少有点懵,下面笔者就以自己的理解,站在高处总结一下,希望有助于大家更好的理解相关概念

所谓原型(prototype),也即js中函数才能拥有的属性,本质上还是一个对象,我们通过构造函数实例化的对象,可以继承来自原型的属性和方法

每个实例对象的__proto__指向其原型,借助原型链可以递归式的查询自己原型对象的原型,进而使实例化对象继承原型对象的原型中的属性和方法(实际上我们输出对象的某个属性值,在其原型上找不到就会借助原型链到其原型的原型上去找,直到null)

现在再来看当初封装的new方法

function _new (constructor,params) {
     //创建一个空对象
     var obj ={}
     //让空对象的原型指向构造函数的prototype属性
     obj.__proto__ = constructor.prototype
     //执行构造函数
     constructor.apply(obj,params)
     //返回obj
     return obj
}

为什么要obj.__proto__ =constructor.prototype??

因为实例化的obj对象要继承构造函数原型对象的属性和方法

为什么要constructor.apply(obj)??

因为要使得构造函数中this指向obj,这样构造函数中属性才会挂载到obj上去

执行测试:

image.png

后记

到这里大致把js面向对象说了一遍,但其实可能还会有人疑惑与prototype的作用到底是什么?

存在这种疑惑挺正常,因为我认为prototype真正发挥巨大作用的地方在于继承

function UserError(message) {
  this.message = message || '默认信息';
  this.name = 'UserError';
}

UserError.prototype = new Error();
UserError.prototype.constructor = UserError;

这里创建一个构造函数UserError,目的是想自定义一个错误类型

然后我们通过UserError.prototype =new Error()

使得以后由UserError实例的对象都能继承来自于Error对象实例的属性和方法

理解较为粗浅,欢迎各位读者批评指正,互相交流,共同学习

后记(2022-03-17)

前端时间参加支付宝前端训练营的考试,一道题上发现自己对于prototype和__proto__认知存在一定的问题,又查阅了相关资料,特此总结

image.png

对于选项A,每个对象都有一个原型对象。(我当认为A错是认为只有函数才有原型对象)(而之所以有这种认知是由于把prototype与原型对象混为一谈)

我之前把原型对象和prototype对等,其实这是不对的,prototype和__proto__其实说白了都是属性,是一个指针,都可以指向原型对象。

原型对象的概念不是由prototype和__proto__定义的,而是由V8引擎提供的。例如我们使用自定义的构造函数实例对象时,V8引擎会为其提供一个空对象作为原型对象,而我们使用Object()构造函数实例对象时,V8引擎会提供Object.prototype作为原型对象(上面V8引擎原生挂载了一些属性和方法)(可自行控制台打印查看)

最后,A错误的原因是因为原型链是有尽头的,自定义构造函数的原型对象也是对象,它的原型对象是object,而object的原型对象是null。而null(原型链尽头)是没有原型对象的。或者说Object.create(null)创建出的对象是没有原型对象的,因为其原型对象为null

对于B,函数fn可以通过fn.prototype来访问其原型对象。

这里应该是错误的(当然这道题多少有点为了考察而去考察的意思),第一眼认为它对是因为本能的把function和prototype关联在一起,但其实function也是一个对象,其由Function构造函数构造出来,所以其原型对象应该为fn.__proto__

查资料时在github上看见一个很不错的issues(里面讨论了先有Object还是现有Function的问题,并且给Function.prototype===Function.__proto__一个比较科学的解释)有趣的issues

对于C,十分中肯的原型链解释,不再说了

对于D,也错的很明显,实例函数的__proto__应该是指向其原型对象,它的constructor才是指向其构造函数