构造函数的应用
在我们类这个概念还没有出来之前,我们都是通过构造函数的方法来创建实例化对象。
我们先来看看传统创建对象和构造函数创建的区别。
//1.通过对象字面量来创建。 var student = { name: "zhangsan", age: 18, gender: "male", sayHi: function () { console.log("hi,my name is " + this.name); }, }; //2.通过 new Object() 创建对象。 var student = new Object(); student.name = "zhangsan", student.age = 18, student.gender = "male", student.sayHi = function () { console.log("hi,my name is " + this.name); };
上面两种都是简单的创建对象的方式,那如果是一个班的学生,n 个学生呢?
显然如果这样做的话代码冗余率太高,是不可取的。我们也学过函数,
所以简单方式的改进是:工厂函数。
//3.通过工厂函数来创建对象。function createStudent(name, age, gender) { var student = new Object(); student.name = name; student.age = age; student.gender = gender; student.sayHi = function () { console.log("hi,my name is " + this.name); }; return student; } var s1 = createStudent("zhangsan", 18, "male"); var s2 = createStudent("lisi", 19, "male");
这样封装代码确实解决了代码冗余的问题,但是每次调用函数 createStudent()都会创建新函数sayHi(),也就是说每个对象都有自己的 sayHi() 版本,而事实上,每个对象都共享一个函数。为了解决这个问题,我们引入面向对象编程里 的一个重要概念:构造函数。
//4.通过构造函数来创建对象。 function Student(name, age, gender) { this.name = name; this.age = age; this.gender = gender; this.sayHi = function () { console.log("hi,my name is " + this.name); }; } var s1 = new Student("zhangsan", 18, "male");
来看看构造函数与工厂函数的区别:
1.首先在构造函数内没有创建对象,而是使用 this 关键字,将属性和方法赋给了 this 对象。
2.构造函数内没有 return 语句,this 属性默认下是构造函数的返回值。
3.函数名使用的是大写的 Student。
4.用 new 运算符和类名 Student 创建对象。
构造函数虽然科学,但仍然存在一些问题。
我们使用前面的构造函数例子来讲解(修改了 sayHi() 方法):
function Student(name, age, gender) { this.name = name; this.age = age; this.gender = gender; this.sayHi = function () { console.log("hi"); }; } //首先我们创建两个实例化对象: var s1 = new Student("zhangsan", 18, "male"); s1.sayHi(); // 打印 hi var s2 = new Student("lisi", 18, "male"); s2.sayHi(); // 打印 hi console.log(s1.sayHi == s2.sayhi); // 结果为 false
由于每个对象都是由 new Student 创建出来的,因此每创建一个对象,函数
sayHi() 都会被重新创建一次,这个时候,每个对象都拥有一个独立的,但是功
能完全相同的方法,这样势必会造成内存浪费。有的人可能会想,既然是一样的
那我们就单独把它提出来,写一个函数,每次调用不就可以了吗?比如:
function sayHi() { console.log("hi"); } function Student(name, age, gender) { this.name = name; this.age = age; this.gender = gender; this.sayHi = sayHi; } var s1 = new Student("zhangsan", 18, "male"); s1.sayHi(); // 打印 hi var s2 = new Student("lisi", 18, "male"); s2.sayHi(); // 打印 hi console.log(s1.sayHi == s2.sayHi); // 结果为 true
但是这样做会导致全局变量增多,可能会引起命名冲突,代码结果混乱,
维护困难。通过使用原型可以很好的解决这个问题。
原型的作用
原型:prototype
在 JavaScript 中,每一个构造函数都有一个 prototype 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
function Star(uname,age) { this.uname = uname; this.age = age; } let dgl = new Star("戴古拉k",22); console.log(dgl); console.log(dgl.__proto__);
而在我们的实例对象中具有__proto__属性,它指向的是我们构造函数中的prototype原型对象
function Star(uname,age) { this.uname = uname; this.age = age; Star.prototype.movie = function() { console.log("极地追击"); } } var dgl = new Star("戴古拉k",22); console.log(dgl); dgl.movie();
因此当我们调用的时候__proto__直接帮我们指向了prototype中的movie属性
在构造函数的prototype中存在一个constructor属性,它指向我们prototype原型对象所在的构造函数
注意我们使用prototype创建方法时都是prototype + .属性 ,如果我们使用的是对象添加方法则constructor会不一样
function Star(uname,age) { this.uname = uname; this.age = age; Star.prototype.movie = function() { console.log("极地追击"); } } let dgl = new Star("戴古拉k",22); console.log(dgl); console.log(dgl.__proto__.constructor);
function Star(uname,age) { this.uname = uname; this.age = age; Star.prototype = { movie: function() { console.log("极地追击"); } } } let dgl = new Star("戴古拉k",22); console.log(dgl); console.log(dgl.__proto__.constructor);
上图可看两种方式得到的构造函数不一样,因为我们用{}创建时会覆盖掉原来的constructor属性,因此得到的结果不同
构造函数、实例、原型三者之间的关系
每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针 constructor,而实例都包含一个指向原型对象的内部指针__proto__。
原型链
在我们的构造函数的prototype原型对象中还有一个Object原型对象以及__proto__属性,__proto__属性指向Object 原型对象而Object通过constructor又指向Object构造函数,如此形成一条链式结构,即原型链
属性查找原则