第十七节JavaScript 构造函数与原型
一. JS对象复习
1. 对象的创建
1.1. 字面量方式(也叫直接量)
var obj = {};
1.2 new操作符创建对象
var obj = new Object()
2.对象属性和方法
2.1 对象的属性
对象有属性,所谓的属性就是这个对象的特点、特性
var student = {};
student.sex = '男孩'
2.2 对象的方法
对象只能有属性,只是如果这个属性的值是一个函数,那么这个函数我们就称为对象的“方法”(method)。
student.study = function(){
console.log('我学会了语文')
}
二. 构造函数
构造函数就是用来创造新对象的, 它必须用关键字new来创造,如果将构造函数用作普通函数的话,往往不会正常工作的. 按照一惯的约定, 我们开发者把构造函数的首字母大写用作辨别. 一个构造函数创造的对象被称为该构造函数的实例
常见的内置构造函数:
Object()
Array()
RegExp()
Function()
Date() //创建日期对象
1. 构造函数的原理
JavaScript规定,一个函数可以用new关键字来调用。那么此时将按顺序发生四件事情:
1)隐秘的创建一个新的空对象
2)将这个函数里面的this绑定到刚才创建隐秘新对象上
3)执行函数体里面的语句
4)返回这个新的对象
例子:
function People(){
this.name = "小明";
this.age = 18;
this.sex = "男";
}// 使用new关键字来调用函数,那么函数就叫做构造函数,将返回一个对象
var xiaoming = new People();
console.log(xiaoming); // People {name: "小明", age: 18, sex: "男"}
console.log(xiaoming.age); //18
console.log(typeof xiaoming); //object
构造函数是使用new关键字来调用,生成实例对象
厉害了,现在我们发现函数不仅仅能够执行,还能返回出来一个对象。也就是说,对象是函数“生”出来的,对象是函数“new”出来的。
所以我们称呼这个生出对象的函数,叫做构造函数,一般的,构造函数用大写字母开头。也叫作People“类”。
xiaoming是People类的实例对象。
People是xiaoming对象的构造函数,也叫类。
2. 构造函数类和实例
People这个函数将被new关键词来调用,所以称为构造函数
构造函数里面语句将会被执行返回一个对象,所以new多少次,里面语句就执行多少次,宏观看,返回的所有对象都有name的属性,age属性
类是具有相同属性和方法的集合
我们说,这些返回的对象都有相同的属性群,所以可以看做是一类东西,那么People这个构造函数,也就可以看成类的定义
例子:
function People(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
//new关键字造出来的xiaoming、xiaohong,我们称为People类的实例。可以理解为实体
var xiaoming = new People("小明",18,"男");
var xiaohong = new People("小红",17,"女");
console.log(xiaoming);
console.log(xiaohong);
但是JavaScript中,没有类的概念。是通过构造函数的4步走机制来创建类的对象,可以看为类。JS这个语言是“基于对象”(base Object)的语言,不能叫做“面向对象”(orinted object)的语言。
所以我们的构造函数,也可以看成类的定义:
构造函数其实就是一个普通的函数,JS没有规定函数里写什么,。只不过我们知道new操作符的原理后,就习惯了先用this.'...' = 来罗列所有属性,和方法而已。你一定要深刻理解:
new调用一个函数的时候,函数里面的语句会执行。
function People(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
this.sayHello = function(){
alert("你好我是" + this.name + ",nice to meet you");
}
}
function Dog(name,pinzhong,age,sex){
this.name = name;
this.pinzhong = pinzhong;
this.age = age;
this.sex = sex;
this.wangwang = function(){
alert("汪汪汪汪" + this.name + "汪汪汪汪汪汪");
}
}
var xiaoming = new People("小明",11,"男");
xiaoming.sayHello();
var xiaobai = new Dog("小白","京巴",2,"公的");
xiaobai.wangwang();
3. 构造函数里面的注意事项
3.1 如果构造函数里没有this,那么将创建一个空对象
//构造函数里面如果没有了this,那么就废了,就不能给隐秘新创建出来的对象添加属性。但是里面的语句能够执行!!所以new出来的就是一个空对象
function People(){
for(var i = 1 , sum = 0; i <= 100 ; i++){
sum += i;
}
alert(sum);
}
var xiaoming = new People();
console.log(xiaoming);
3.2 构造函数构造函数中,不允许出现return语句
构造函数总不能出现return, 严格的说是不能return 引用类型数据.如果出现return语句返回引用类型值,那么构造函数将不能返回新创建的那个对象,而是返回return 语句后面的内容
function People(name,age){
this.name = name;
this.age = age;
return {"a":1,"b":2};
}
var xiaoming = new People("小明",12);
console.log(xiaoming);
function aa() {
this.name = "mingzi";
function bb() {
console.log("我是BB");
}
console.log("试试构造函数");
return bb; //返回了 bb函数 覆盖了原有this对象,数组也不行,也是对象的一种
}
var xm = new aa();
console.log(xm);
3.3 JS没有规定构造函数中的语句的规范,想怎么写就怎么写:
// 这就是函数 你想怎么写就怎么写
function People(name,age){
this.name = name;
this.age = age;
// if(this.age >= 18){
// this.state = "成年人";
// }else{
// this.state = "未成年";
// }
this.state = this.age >= 18 ? "成年人" : "未成年";
}
var xiaoming = new People("小明",11);
alert(xiaoming.state);
总结一下
当一个函数用()调用的时候,this就是window
当一个函数用对象调用的时候,this就是这个对象
当一个函数绑定给一个HTML元素事件的时候,this就是这个HTML元素
当一个函数用定时器调用的时候,this就是window
当一个函数用apply、call调用的时候,this就是你指定的第一个参数
当一个函数用new调用的时候,this就是隐秘创建的空对象,函数里面的语句将被执行,并且返回新对象
四. 原型 prototype
1 原型 prototype
prototype就是英语“原型”的意思。原型是构造函数创建对象的原始模型
原型的特点:
原型也是对象,原型是函数对象的一个属性
原型自带constructor属性, constructor指定构造函数
构造函数创建出的对象会继承原型的属性和方法
// 任何函数都有原型,只是原型对于普通函数没什么大用,但对于构造函数用处极大
function fun(){
console.log("您好")
}
console.log(fun.prototype);
console.log(typeof fun.prototype);
在JavaScript中,任何一个函数,都有一个prototype属性,指向一个对象。打印prototype属性,你会发现这个属性指向一个空对象。打印prototype的类型,发现是object类型。
2. 原型对象与实例
一个函数的原型,对于普通函数来说,没任何用。但是如果函数是一个构造函数,那么函数的原型,用处极大!
示例:
function People(name,sex,age){
this.name = name;
this.sex = sex;
this.age = age;
}
People.prototype = {
hight: 180
}
var xiaoming = new People("小明","男",18);
console.log(xiaoming.name); // "小明"
console.log(xiaoming.sex); // "男"
console.log(xiaoming.age); // 18
console.log(xiaoming.hight); // 180
所以我们甚至可以把所有的属性都加在原型上
// 构造函数
function People(){
}
//构造函数的原型,我们更改了构造函数的原型,为一个新的对象:
People.prototype = {
name : "小明",
sex : "男",
age : 18,
hight: 180
}
var xiaoming = new People();
console.log(xiaoming.name); // "小明"
console.log(xiaoming.sex); // "男"
console.log(xiaoming.age); // 18
console.log(xiaoming.hight); // 180
当一个对象被new出来的时候,不仅仅执行了构造函数里面的语句,我们的感觉,构造函数的原型中,所有的属性也加给了这个对象
3. 实例对象的proto 属性
当一个对象被new出来的时候,不仅仅执行了构造函数里面的语句,也会把proto指向构造函数的prototype。
// 构造函数
// 构造函数里面没有任何语句,也就是说,这个构造函数在执行的时候,不会给创建出来的对象添加任何属性。
function People(){
}
//构造函数的原型,我们更改了构造函数的原型,为一个新的对象:
People.prototype = {
name : "小明",
sex : "男",
age : 18,
hight: 180
}
//当一个对象被new出来的时候,不仅仅执行了构造函数里面的语句,也会把__proto__指向构造函数的prototype。
var xiaoming = new People();
//当我们试图访问sex、age属性的时候,身上没有。那么就去查找原型,原型身上有,就当做了自己的属性返回了。
console.log(xiaoming.name);
console.log(xiaoming.sex); //自己没有,找原型对象
console.log(xiaoming.age); //自己没有,找原型对象
console.log(xiaoming.__proto__);
// {name: "小明", sex: "男", age: 18, hight: 180}
console.log(xiaoming.__proto__ == People.prototype);
// true
注意:
任何一个对象,都有proto属性,这个属性是Chrome自己的属性,别的浏览器不兼容,但是别的浏览器也有原型对象,只不过不能通过proto进行访问而已。
这是属性指向自己的原型对象。
构造函数、原型、实例对象之间的关系
<style>
#box {
font: size 32px;
color: red;
}
</style>
</head>
<body>
<h1></h1>
<div id="box">wo shi box
</div>
<script>
//1、原型与构造函数之间的关系
function people(name, age) {
this.name = name;
this.age = age;
}
console.dir(people)
console.dir(people.prototype.constructor === people); //通过函数的属性prototype找到原型,再通过原型.constructor找到函数自己
console.dir(people.prototype.constructor);
console.dir(people);
console.log(people.prototype.constructor);
// prototype对象是people构造函数的原型
// people函数prototype原型对象的构造函数
//2、构造函数与实例对象的关系
var xm = new people('小明', 18)
console.log('xm', xm);
// xm是函数people的实例对象
//people函数时xm对象的构造函数
//3、实例对象与原型对象的关系
console.dir(xm.__proto__ === people.prototype);//实例对象的__proto__属性等于原型
</script>
- 原型的扩展
我们的JavaScript有一个棒的机制:原型链查找。
当我们试图访问一个对象身上的属性的时候,如果这个对象身上有这个属性,则返回它的值。如果它身上没有这个属性,那么将访问它的原型对象,检测它的原型对象身上是否有这个值,如果有返回它原型对象身上的这个值。
任何一个函数都有原型,原型是一个对象,用prototype来访问。
当这个函数是构造函数的时候,new出来的对象,它们的原型对象就是这个构造函数的原型。
prototype我们称为“原型”,只有函数有原型
proto我们称为“原型对象”,任何对象都有原型对象。
- 原型的应用
我们定义一个方法的时候,如果写在构造函数里面:
function People(name,age){
this.name = name;
this.age = age;
this.sayHello = fucntion(){
alert("你好,我是" + this.name + "我今年" + this.age + "岁了");
}
}
var xiaoming = new People("小明",12);
var xiaohong = new People("小红",11);
xiaoming.sayHello();
xiaohong.sayHello();
这样写可以,但是有什么问题呢,内存开太大
在new一个xiaoming的时候,构造函数中的代码顺序执行,绑定了name,age,sayHello,new一个xiaohong的时候也是如此,你有没有发现,不同的对象拥有相同方法.因此可以将复用的方法放在原型对象上.
function People(name,age){
//构造函数里面,负责定义一些属性,随着构造函数的执行,这些属性将绑定到new出来的对象身上
this.name = name;
this.age = age;
}
//把相同的方法,定义在原型对象身上:
People.prototype.sayHello = function(){
alert("你好,我是" + this.name + "我今年" + this.age + "岁了");
}
var xiaoming = new People("小明",12);
var xiaohong = new People("小红",11);
alert(xiaoming.sayHello == xiaohong.sayHello); //true
<style>
#box {
font: size 32px;
color: red;
}
</style>
</head>
<body>
<h1></h1>
<div id="box">wo shi box
</div>
<script>
//函数实例化对象,所有共用的属性就可以放到原型上
function people(name, age) {
this.name = name;
this.age = age;
}
var xm = new people('小明', 19);
var xh = new people('小红', 21);
console.log(xm.__proto__ === xm.__proto__); //xh xm是同一个原型
console.log(xm.sayhello === xh.sayhello);//xh xm sayhello函数不同 将sayhello放到原型上就相等了
people.prototype.sayhello = function () {
console.log("大家好");
}
console.log(xm.__proto__.constructor); //实例对象通过原型找到构造函数
</script>
证明了xiaoming的sayHello方法和xiaohong的,是同一个函数。内存消耗小很多。
示例:
function Grand(){}
Grand.prototype.sex = "male";
var grand = new Grand();
function Father(){
this.age = 50;
}
Father.prototype = grand;
var father = new Father();
function Son(){
this.name = "wuwei";
}
Son.prototype = father;
var son = new Son();
console.log(son.name);
console.log(son.age);
console.log(son.sex);
console.log(son.toString()); //[object Object]
五.创建对象的常见的几种模式
1、工厂模式
⼯⼚模式是软件⼯程领域⼀种⼴为⼈知的设计模式,这种模式抽象了创建 具体对象的过程
function createPerson(name,age,family) {
var o = new Object();
o.name = name;
o.age = age;
o.family = family;
o.say = function(){
alert(this.name);
}
return o;
}
var person1 = createPerson("lisi",21,["lida","lier","wangwu"]); //instanceof无法判断它是谁的实例,只能判断他是对象,构造函数都可以判断出
var person2 = createPerson("wangwu",18,["lida","lier","lisi"]);
console.log(person1 instanceof Object); //true
console.log(person1 instanceof createPerson); //false 工厂模式这个对象不知道谁创建出来的自己,instanceof判断前面的对象是不是后面的函数构造出来的
⼯⼚模式虽然解决了创建多个相似对象的问 题,但没有解决对象识别的问题(但是工厂模式却无从识别对象的类型,因为全部都是Object,不像Date、Array等,本例中,得到的都是o对象,对象的类型都是Object,因此出现了构造函数模式)。工厂模式是自己写的结构,构造函数模式自带结构。
2、构造函数模式
function Person(name,age,family) {
this.name = name;
this.age = age;
this.family = family;
this.say = function(){
alert(this.name);
}
}
var person1 = new Person("lisi",21,["lida","lier","wangwu"]);
var person2 = new Person("lisi",21,["lida","lier","lisi"]);
console.log(person1 instanceof Object); //true
console.log(person1 instanceof Person); //true
console.log(person2 instanceof Object); //true
console.log(person2 instanceof Person); //true // 判断xm是通过哪个函数创建出来的对象
console.log(person1.constructor); //constructor 属性返回对创建此对象的数组、函数的引用
对比工厂模式有以下不同之处:
1、没有显式地创建对象
2、直接将属性和方法赋给了 this 对象
3、没有 return 语句
可以看出,构造函数知道自己从哪里来(通过 instanceof 可以看出其既是Object的实例,又是Person的实例)
构造函数模式的缺点: 就是每个⽅法都要在每个实例上重新创建⼀遍
3、原型模式
借助函数的原型,将一些实例对象共享的属性和方法放在原型对象中, 这样就不必在构造函数中定义重复的方法
function Person() {
}
Person.prototype.name = "lisi";
Person.prototype.age = 21;
Person.prototype.family = ["lida","lier","wangwu"];
Person.prototype.say = function(){
alert(this.name);
};
console.log(Person.prototype); //Object{name: 'lisi', age: 21, family: Array[3]}
var person1 = new Person(); //创建一个实例person1
console.log(person1.name); //lisi
var person2 = new Person(); //创建实例person2
person2.name = "wangwu";
person2.family = ["lida","lier","lisi"];
console.log(person2); //Person {name: "wangwu", family: Array[3]}
// console.log(person2.prototype.name); //报错
console.log(person2.age); //21
原型模式的好处是所有对象实例共享它的属性和方法(即所谓的共有属性),
缺点就是省略了为构造函数传递初始值参数,导致所有的实例对象都是相同的属性和方法,
4、混合模式(构造函数模式+原型模式)
构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性
function people(name, age) {
this.name = name;
this.age = age;
}
//没有修改构造函数默认的原型对象,有默认的constructor属性,是灰色的。但是添加属性不方便
people.prototype.play = function () {
"我会王者荣耀"
}
people.prototype.like = function () {
"我喜欢看书,乒乓球"
}
var xh = new people("小红", 19);
var xm = new people("小明", 18);
console.log(xm);
console.log(xh
)
function Person(name, age, family) {
this.name = name;
this.age = age;
}
Person.prototype = {
constructor: Person, //将一个对象赋值给prototype对象,constructor属性需要手动添加,不是默认的。每个函数都有prototype属性,指向该函数原型对象,原型对象都有constructor属性,这是一个指向prototype属性所在函数的指针
say: function () {
alert(this.name);
},//对象属性之间用,号
play: function () {
console.log("我很喜欢玩游戏");
}
}
var person1 = new Person("lisi", 21);
console.log(person1);
var person2 = new Person("wangwu", 21);
console.log(person2);
可以看出,混合模式共享着对相同方法的引用,又保证了每个实例有自己的私有属性。最大限度的节省了内存
高程中还提到了动态原型模式,寄生构造函数模式,稳妥构造函数模式, 这些就自己读一读, 了解一下