JavaScript构造函数学习笔记

xiaoxiao2021-02-28  16

JavaScript构造函数学习笔记

在JavaScript中,创建对象一般可以通过两种方式

对象字面量,在JavaScript中用大括号括起来的键值对就是对象。所以可以直接通过这种方法创建对象:

var book = { name: "明朝那些事儿", author:"当年明月", setName: function(name) { this.name = name; } }

这种方式间接明了,但是对于每个对象都要重复定义,并且对于对象的共性也就是类,没有体现。

构造函数,顾名思义,是用来创建对象的函数。但是在JavaScript中的这个函数却并没有和其他的函数有所区分。任何函数,只要通过new来调用,都可以当做构造函数。当然了,编译器没有区分,可是我们还是区分一下更加的明了。所以,按照惯例,我们一般讲构造函数的首字母大写。

function Book(name, author) { this.name = name; this.author = author; this.setName = function(name) { this.name = name; } } var myBook = new Book("明朝那些事儿", "当年明月");

在使用new操作符的过程中,发生了这几件事:

创建一个新的对象将新对象的__proto__ 指向构造函数的prototype将构造函数的作用域赋给新对象(因此构造函数中的this指向新的对象)执行构造函数中的代码返回新的对象

具体成代码就是:

var myBook0 = {}; myBook0.__proto__ = Book.prototype;//直接通过字面量创建的对象的__proto默认都是指向object的 Book.call(myBook0);//这里还需要根据实际情况传入参数 return myBook0;

第二步也就是对于__proto__ 的指向的修改,是我们不应该手动修改的。因为__proto__ 并不是JavaScript标准中的属性,只是某些浏览器添加的方便我们操作而已,而且在一些浏览器中并没有这个属性。__proto__ 其实对应于JavaScript标准中的[[prototype]]属性,然而这个标准的属性只能够编译器访问,这也是有些浏览器添加了这个属性的原因。

第三步中的作用域还有些问题。如果是正常的使用new来对对象进行创建,作用域当然是在新生成的对象上了。(对于作用域有问题的可以看这里) 但是,当没有使用new来使用构造函数的时候,也就是直接调用的时候,将会按照普通函数调用时的作用域来执行,而其中this的的指向是和声明环境无关的,也就是说和作用域是不一样的,this在JavaScript中的指向和执行环境相关,也就是this所在函数执行时的当前对象。除了以上两种调用方式,作为一个函数当然也是可以使用call或者apply调用的。

var book1 = new Book("JavaScript权威指南", "Nicholas"); console.log(book1.author);//Nicholas Book("射雕英雄传", "金庸"); console.log(author);//金庸 var book2 = {}; Book.call(book2, "雪山飞狐", "金庸"); console.log(book2.name);//雪山飞狐

相比较于通过字面量创建对象,使用构造函数创建函数有这些不同:

没有显式的创建对象创造出来的对象的__proto__ 属性指向构造函数的prototype新对象中的constructor属性指向构造函数

这些不同使得我们创建对象方便了许多,可是使用constructor创建对象仍然有些不足之处。

var b1 = new Book(); var b2 = new Book(); console.log(b1.setName === b2.setName);//false

在构造函数中对于setName,我们的定义是这样的:

this.setName = function(name) { this.name = name; }

我们知道,函数也是一种对象,所以对于函数的定义其实也相当于声明了一个对象。需要注意的是在每次调用构造函数时都会创建这个对象,但是我们在所有的实例中使用的函数功能却是一样的,这就造成了内存的浪费。

发现了问题肯定是要解决的。既然由于多次声明造成了内存的浪费,那么声明一次不就好了。我们改成了这样:

function Book(name, author) { this.name = name; this.author = author; this.setName = setName; } function setName(name) { this.name = name; }

这样固然节省了内存,但是这个setName谁都能用,又违背了面向对象封装的思想。

解决办法还是有滴。我们知道在对象上查找某个属性或者方法时,会顺着该对象所在的原型链向上查找,那么我们只需要把所有实例共有的属性或者方法放到原型上去就好了。

使用构造函数时对于__proto__ 指向的修改,这时就派上用场了。

function Book(name, author) { this.name = name; this.author = author; } Book.prototype = { constructor : Book, setName : function(name) { this.name = name; } }

由于所有的实例的__proto__ 属性都是指向Book.prototype的,所以只需要修改Book的prototype的指向就可以了。

需要注意的是在prototype中还有一个默认的constructor属性,不要忘了把它指回原构造函数,否则使用instanceof判断是否是实例将会出错。

转载请注明原文地址: https://www.6miu.com/read-2450078.html

最新回复(0)