初识JavaScript(二十)
1 构造函数的执行过程
构造函数的执行过程:
通过代码调试:debug过程。
断点调试:breakpoint
bug:代表了程序中的错误。原意是臭虫的意思。
调试按钮:
step:逐步执行代码,执行下一行代码。遇到方法调用,会进入方法内执行。
step out:跳出当前方法,执行方法后的代码。
step into:进入下一个方法执行。
step over:逐步执行代码,执行下一行代码。遇到方法调用,不会进入执行。
构造方法的执行过程:
1:实参给形参赋值,js底层创建好空对象,并赋值给this。
2:通过this给对象绑定属性。绑定方法。
3:绑定属性结束之后,对象初始化完毕。
4:返回this 赋值给变量。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function Student(name,age,score) {
//给this绑定属性的过程。
this.name = name;
this.age = age;
this.score = score;
this.study = function () {
console.log (this.name + "热爱学习");
};
this.eat = function () {
console.log (this.name + "是个吃货");
}
}
console.log ("hello");
console.log ("hello");
console.log ("hello");
var student = new Student("小白",10,100);
student.study();
</script>
</body>
</html>
2 构造函数的优缺点
构造函数创建对象的优缺点:
优点:
1:构造函数提供了一个模板,通过该模板,可以快速方便的创建对象。
2:可以更加明确创建对象的类型。
缺点:
不同的对象之间的属性和方法都是不共享的,当时通过同一个构造方法创建的对象的功能是完全一样的。不同对象可以共享同一个方法。这样更加节约内存。
缺点:不同对象相同的方法不能共享使用。
如何让同类型的不同的对象共享方法?
解决该问题的方法:
将需要共享的方法放到一个所有的对象共享的一个内存空间中保存。
如果对象需要使用方法的时候,去共享空间中去查找使用即可。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function Student(name,age,score) {
//给this绑定属性的过程。
this.name = name;
this.age = age;
this.score = score;
this.study = function () {
console.log (this.name + "热爱学习");
};
this.eat = function () {
console.log (this.name + "是个吃货");
}
}
var bai = new Student("小白",18,100);
var qing = new Student("小青",20,100);
</script>
</body>
</html>
3 函数的原型对象
函数的原型对象:
函数的定义是具有唯一性的,任何一个函数所占用的内存空间是唯一的。
任何的函数都有一个属性 prototye 。该属性 prototype 指向了一个对象。该对象就称为[函数的原型对象],简称 [函数原型]. 任何的函数的原型 都是具有唯一性的,有自己唯一的内存空间。
函数原型中有一个属性:constructor。指向了该原型对象的构造函数。
将对象需要共享的功能添加到构造函数的原型中。实现多个对象共享相同的方法。
使用原型中定义的功能:直接使用对象调用方法即可。
通过构造函数创建的任何对象对象中都有一个属性:[prototype] 该属性在规范中并没有规定必须提供访问它的方式。该属性指向了对象的构造函数的原型对象。对象可以通过该属性去访问原型中的内容。
将需要共享的函数添加到构造函数的原型中。通过构造函数创建的对象通过对象内的[prototype]就可以访问原型中添加的方法了。
在谷歌和火狐的浏览器中,对象提供了 proto 属性来访问对象的构造函数的原型对象。
总结:
希望每个对象都有自己独立的内存的属性需要在构造函数内定义。
希望所有对象共享的部分添加在构造函数的原型中去。
通常情况下,属性都是定义在构造函数内的。称为对象的私有属性。如果是对象共有的属性,那么也可以定义在原型中。
方法需要定义在原型中。
原型对象中添加的属性和方法直接使用 对象 访问即可。【不需要添加 propt】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function Student(name,age,score) {
//给this绑定属性的过程。
this.name = name;
this.age = age;
this.score = score;
// this.study = function () {
// console.log (this.name + "热爱学习");
// };
// this.eat = function () {
// console.log (this.name + "是个吃货");
// }
}
//Student函数原型
console.log (Student.prototype);
//函数的原型中的constructor 和 构造函数比较
console.log (Student === Student.prototype.constructor);//true
//将对象需要共享的功能添加到构造函数的原型中。
Student.prototype.study = function () {
console.log (this.name + "热爱学习");
};
Student.prototype["eat"] = function () {
console.log (this.name + "是个吃货");
}
//在原型对象中添加共有的属性。
Student.prototype.country = "中国";
//使用对象调用原型中定义的方法。
var bai = new Student("小白",18,100);
var qing = new Student("小青",17,100);
bai.study();
bai.eat();
qing.study();
qing.eat();
// 两个对象共享同一个方法
console.log (bai.study === qing.study);//true
console.log (bai.__proto__ === Student.prototype);//true
console.log (qing.__proto__ === Student.prototype);//true
</script>
</body>
</html>
4 为何对象可以访问原型中的内容?
构造函数创建的对象为何可以直接访问构造函数的原型中的内容?
1:当通过对象去访问一个方法或者是属性的时候,首先去自己的私有属性中去查找,如果私有属性中包含了被访问的内容,就直接访问。
2:如果私有属性中没有要访问的内容。那么就顺着对象的 [prototype](谷歌火狐中的__proto__)指向的原型对象中去查找。如果有,就直接访问。
3:如果原型对象中也没有要访问的内容,那么就去原型中的原型对象中去查找,重复上述的过程,直到找到为止,如果找到Object 根对象的原型中都没有找到,那么就找不到该内容了,报错了。
4: 那么整个查找的过程,就是顺着[prototype][proto]链条向根查找。 【原型链】。
注意: Object 的原型对象的原型为 null。
Object 构造函数的原型中定义了所有的对象的一些通用的方法。底层就在创建对象的时候,将Object的原型对象添加到了创建对象的原型链的尾部。
原型中定义的方法都是实例方法,都是通过对象实例来访问的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function Student(name,age,score) {
//给this绑定属性的过程。
this.name = name;
this.age = age;
this.score = score;
// this.study = function () {
// console.log (this.name + "热爱学习");
// };
// this.eat = function () {
// console.log (this.name + "是个吃货");
// }
}
//Student函数原型
console.log (Student.prototype);
//函数的原型中的constructor 和 构造函数比较
console.log (Student === Student.prototype.constructor);//true
//将对象需要共享的功能添加到构造函数的原型中。
Student.prototype.study = function () {
console.log (this.name + "热爱学习");
};
Student.prototype["eat"] = function () {
console.log (this.name + "是个吃货");
}
//在原型对象中添加共有的属性。
Student.prototype.country = "中国";
//使用对象调用原型中定义的方法。
var bai = new Student("小白",18,100);
bai.study();
bai.eat();
// bai.country;
//Object 构造函数的原型中定义的方法,可以被直接使用。
console.log (bai.toString());//[object Object]
//Object 的原型对象的原型为null。
console.log (Object.prototype.__proto__);
</script>
</body>
</html>
5 this的阶段总结
this的阶段总结:
1:在全局作用域下的this,代表window对象。
2:在构造函数创建对象的时候,里面的this代表刚刚创建好的对象。
3:原型对象中添加的方法中的this。调用该方法的实例对象。
4:构造函数中定义的方法中的this。调用该方法的实例对象。
this总结:this代表当前环境下的对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function Student(name,age,score) {
this.name = name;
this.age = age;
this.score = score;
this.eat = function () {
console.log (this);
}
}
Student.prototype.study = function () {
console.log (this.name + "热爱学习");
};
var bai = new Student("小白",18,100);
bai.study();
bai.eat();
var qing = new Student("小青",18,100);
qing.study();
qing.eat();
</script>
</body>
</html>
6 原型练习
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
1: 定义等边三角形的构造函数。属性:一个边长。方法:周长+面积。
功能都添加到原型中去。
创建实例,测试功能。
2:给String添加实例方法,reverse(). 翻转当前对象。
3:给String添加实例方法。sort().对当前字符创对象排序。
-->
<script>
function Triangle(length) {
this.length = length;
}
Triangle.prototype.perimeter = function () {
return this.length * 3;
}
Triangle.prototype.area = function () {
var height = this.length * Math.sqrt (3) / 2;
return this.length * height / 2;
}
String.prototype.reverse = function () {
return this.split("").reverse().join("");
}
console.log ("abcd".reverse());
String.prototype.sort = function () {
return this.split("").sort().join("");
}
console.log ("bd1a5c6ef".sort());
</script>
</body>
</html>
7 js中的继承
js中的继承是基于原型的继承。
js中的继承的方式:3种。
a:借用构造函数继承
b:原型链继承
***c:组合继承
继承的最直接的好处:代码的复用。
继承:inheritance。
概念:
父对象:被继承者。
子对象:继承者。
继承的概念:
子对象可以直接使用父对象中的内容的过程。
父类型:描述的对象的范围更广。
子类型:描述的对象的范围小。但是功能和属性更多。
instancof:用来判断一个对象是否是某种类型的实例对象的。
例子:在前面的练习中,学生对象可以直接使用Object原型中的功能。学生对象隐式的继承了Object原型中的内容。
结论:js中的所有的对象,都直接的或者间接的继承了Object的原型中的内容。除了Object的原型对象本身。这样保证了所有的对象都可以访问Object原型中定义的方法。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function Person(name,age,gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
Person.prototype.eat = function () {
console.log (this.name + "\t是一个吃货!");
}
function Student(name,age,gender,classId,score) {
this.name = name;
this.age = age;
this.gender = gender;
this.classId = classId;
this.score = score;
}
Student.prototype.eat = function () {
console.log (this.name + "\t是一个吃货!");
}
Student.prototype.study = function () {
console.log (this.name + "\t是一个学霸!");
}
console.log (new Person() instanceof Object);//true
console.log (new Student() instanceof Person);//false
</script>
</body>
</html>
8 借用构造函数继承
借用构造函数继承:
在子类型的构造函数内借用父类型的构造函数来使用。
通过测试结论:
1:子类型可以继承父类型构造函数中定义的所有的私有成员(属性+功能)。
2:子类型不能继承父类型构造函数原型中的内容。
借用构造函数继承的方式存在一定的缺陷。
借用构造函数继承,使用instanceof判断。子类对象不是所谓的父类类型的实例。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
this.sleep = function () {
console.log (this.name + "\t爱睡大觉!");
}
}
Person.prototype.eat = function () {
console.log (this.name + "\t是一个吃货!");
}
function Student(name, age, gender, classId, score) {
//借用构造函数继承。
Person.call(this,name,age,gender);
this.classId = classId;
this.score = score;
}
// Student.prototype.eat = function () {
// console.log (this.name + "\t是一个吃货!");
// }
Student.prototype.study = function () {
console.log (this.name + "\t是一个学霸!");
}
//创建子类对象:
var stu = new Student("小白",19,"男","0001",100);
console.log (stu);
//**************
console.log (stu instanceof Person);//false.
</script>
</body>
</html>
9 原型链继承
js的第二种继承方式:原型链继承。
原型链:任何函数的原型对象中也有一个 [prototype](谷歌火狐中叫__proto__)对象。指向了原型的原型对象。原型的原型对象中还有原型对象。到最顶端的Object对象的原型是没有原型对象的。组成了一个由
[prototype](谷歌火狐中叫__proto__)属性链接起来的【原型对象的链条—原型链】。
原型链继承的关键步骤:让子类型的构造函数的原型指向父类型的一个实例。
原型链继承的问题:
1:不能继承父类型的构造函数中的私有成员。
2:子类型的构造函数的原型对象的constructor属性指向了父类型构造函数。必须修正。
补充:instanceof 的作用:右边的类型的原型对象是否在左边的对象的原型链上,在返回true,否则false。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
Person.prototype.eat = function () {
console.log (this.name + "\t是一个吃货!");
}
function Student(name, age, gender, classId, score) {
this.name = name;
this.age = age;
this.gender = gender;
this.classId = classId;
this.score = score;
}
//原型链继承的实现:Student的原型指向Person的一个实例。
//不要传参数。
Student.prototype = new Person(/*"小甜甜",17,"女"*/);
//做原型的构造函数的修正。
Student.prototype.constructor = Student;
//让进行上面最重要的那一步,然后再在子类型的原型中添加必要的功能。
Student.prototype.study = function () {
console.log (this.name + "\t是一个学霸!");
}
var stu = new Student("牛嫂",37,"女","0002",60);
console.log (Student.prototype.constructor === Student);
console.log (stu instanceof Person);//true
</script>
</body>
</html>
10 组合继承
组合继承:将借用构造函数继承和原型链继承组合使用。
借用构造函数继承:不能继承父构造函数原型中的内容。
原型链继承:不能继承父构造函数中的私有成员。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
//父类型的构造函数。
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
Person.prototype.eat = function () {
console.log (this.name + "\t是一个吃货!");
}
function Student(name, age, gender, classId, score) {
//借用构造函数继承。
Person.call(this,name,age,gender);
this.classId = classId;
this.score = score;
}
//原型链继承
//子构造函数的原型指向父类型的一个实例。不需要传参
Student.prototype = new Person();
//子构造函数原型的constructor属性的修正。
Student.prototype.constructor = Student;
//添加其他的方法。
Student.prototype.study = function () {
console.log (this.name + "\t是一个学霸");
}
var stu = new Student("紫霞仙子",27,"女","0001",90);
console.log (Student.prototype.constructor === Student);//true
</script>
</body>
</html>
11 类型的静态方法
函数的分类:
实例函数:对象调用。
静态函数:类型调用。通常是该类型的工具方法。
Array.isArray(arr);
Math.random().
Date.now()
String.fromCharCode()
原型中定义的函数都是实例函数。对象调用。
//如何在自定义的类型中添加静态成员。
语法:
构造函数名.方法名 = function(){}
调用:
构造函数名.方法名(实参);
构造函数的静态属性:是该类型的属性,
结论:类型的静态成员是该类型的方法和属性,不属于类型实例的属性和方法。
静态的成员在内存中只有唯一的一份内存空间。因为不依赖于每一个对象,依赖于构造函数。相当于给构造函数对象添加的属性和方法。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
console.log (Number.MIN_VALUE);//静态属性
//父类型的构造函数。
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
Person.prototype.eat = function () {
console.log (this.name + "\t是一个吃货!");
}
function Student(name, age, gender, classId, score) {
//借用构造函数继承。
Person.call(this,name,age,gender);
this.classId = classId;
this.score = score;
}
//原型链继承
//子构造函数的原型指向父类型的一个实例。不需要传参
Student.prototype = new Person();
//子构造函数原型的constructor属性的修正。
Student.prototype.constructor = Student;
//添加其他的方法。
Student.prototype.study = function () {
console.log (this.name + "\t是一个学霸");
}
var stu = new Student("紫霞仙子",27,"女","0001",90);
//需求:定义静态方法,来判断任意变量是否是Person的实例的。
Person.isPerson = function (obj) {
return obj instanceof Person;
}
console.log (Person.isPerson(stu))//true
//类型的属性。
Person.MAX_AGE = 150;
</script>
</body>
</html>