第一章:JavaScript 是一个编程语言
-
解析执行
:解释一行执行一行。执行速度慢
编译执行
:一次性把代码编译成可执行的代码,然后再一行一行的执行。执行速度较快
-
语言特点:动态,头等函数 (First-class Function)
- 又称函数是 JavaScript 中的一等公民
-
执行环境:在宿主环境(host environment)下运行,浏览器是最常见的 JavaScript 宿主环境
- 但是在很多非浏览器环境中也使用 JavaScript ,例如 node.js
JavaScript 的组成
- ECMAScript - 语法规范
- 变量、数据类型、类型转换、操作符
- 流程控制语句:判断、循环语句
- 数组、函数、作用域、预解析
- 对象、属性、方法、简单类型和复杂类型的区别
- 内置对象:Math、Date、Array,基本包装类型String、Number、Boolean
- Web APIs
- BOM
- onload页面加载事件,window顶级对象
- 定时器
- location、history
- DOM
- 获取页面元素,注册事件
- 属性操作,样式操作
- 节点属性,节点层级
- 动态创建元素
- 事件:注册事件的方式、事件的三个阶段、事件对象
- BOM
JavaScript 可以做什么
浏览器是如何工作的
先找域名对应的ip地址,找到ip地址后
浏览器向ip地址对应的服务器发送请求,服务器将请求的内容返回给浏览器(一般是html的网页)
而通过网络接收到的就是一些字符串,这些字符串需要解析器执行里面的代码。需要了解浏览器的组成部分
-
User Interface
用户界面
,我们所看到的浏览器 -
Browser engine
浏览器引擎
,(需要和渲染引擎进行交互)用来查询和操作渲染引擎 -
Rendering engine
渲染引擎
,负责渲染HTML、CSS,并且生成DOM树`(DOM树存储在内存中)-
Networking
网络
,负责发送网络请求,接收服务器返回的内容。-
接收的内容就是一些字符串,里面包含:
html/css/js
,然后将这些字符串交给渲染引擎, -
渲染引擎会解析html和css,
生成DOM树
。然后再来执行文档里面的js代码
。 -
在执行js代码的时候,我们有时会写一些对话框。这些对话框由
UI Backend
进行处理。它是负责和操作系统进行交互。
-
我们有时可能要存储一些数据,这些数据交给
Data Persistence 数据持久层
进行处理。
-
-
JavaScript Interpreter
JavaScript解析器
,负责执行JavaScript的代码。处理DOM和BOM
如果是DOM操作的话,会更新内存中的
DOM树
,然后渲染引擎会重新渲染DOM树
,接着通知浏览器引擎,将更新的
DOM树
渲染到页面 -
UI Backend
UI后端
:负责和操作系统进行交互,用来绘制类似组合框和弹出窗口
-
-
Data Persistence(持久化) 数据持久化,数据存储 cookie、HTML5中的sessionStorage
JavaScript 执行过程
JavaScript 运行分为两个阶段:
-
第一步:预解析
-
先
执行全局预解析(所有变量和函数声明都会提前;同名的函数和变量函数的优先级高) -
然
后
执行函数内部预解析(所有的变量
、函数
和形参
都会参与预解析)找到:
函数
、形参
、普通变量
-
-
第二步:执行代码
先预解析全局作用域,然后执行全局作用域中的代码,
在执行全局代码的过程中遇到函数调用就会先进行函数预解析,然后再执行函数内代码。
第二章:JavaScript 面向对象编程
面向对象介绍
什么是对象
对象到底是什么,我们可以从两次层次来理解。
(1) 现实中的对象:对象是单个事物的抽象。
- 实体:一本书、一辆汽车、一个人都可以是对象
- 虚体:一个数据库、一张网页、一个与远程服务器的连接也可以是对象。
当实物被抽象成对象,实物之间的关系就变成了对象之间的关系,从而就可以模拟现实情况,针对对象进行编程
(2) 程序中的对象:对象是一个容器,封装了属性(property)和方法(method)。
属性是对象的状态,方法是对象的行为(完成某种任务)。比如:
- 我们可以把动物抽象为animal对象
- 使用“属性”记录具体是那一种动物
- 使用“方法”表示动物的某种行为(奔跑、捕猎、休息等等)。
温馨提示
:
- 每个对象都是基于一个引用类型创建的,这些类型可以是系统内置的原生类型,如:
数组对象
- 也可以是开发人员自定义的类型:
构造函数创建对象
什么是面向对象
面向对象不是新的东西,它只是过程式代码的一种高度封装,目的在于
提高代码的开发效率和可维护性
。
面向对象编程 —— Object Oriented Programming,简称 OOP ,是一种编程开发思想。
它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。
因此,面向对象编程具有灵活、代码可复用、高度模块化等特点,容易维护和开发
面向对象与面向过程的区别:
- 面向过程就是亲力亲为,事无巨细,面面俱到,步步紧跟,有条不紊
- 面向对象就是找一个对象,指挥得结果
- 面向对象将执行者转变成指挥者
面向对象不是面向过程的替代,而是面向过程的封装
面向对象的特性:
- 封装性
- 继承性
- [多态性]抽象
扩展阅读:
面向对象开发的好处:
- 多人协同开发
- 方便代码维护和更新。(哪里有错,就找哪个对象)
程序中面向对象的基本体现
如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,
而是 Student
这种数据类型应该被视为一个对象,这个对象拥有 name
和 score
这两个属性(Property)。
如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个 printScore
消息,让对象自己把自己的数据打印出来。
<script>
// 打印学生的成绩表
// 1 面向过程的方式
// 1.1 记录学生的成绩
var stu1 = {name: 'zs', subject: '语文', score: 90};
var stu2 = {name: 'ls', subject: '语文', score: 80};
// 1.2 打印学生的成绩
console.log(stu1.name, stu1.subject, stu1.score);
console.log(stu2.name, stu2.subject, stu2.score);
//2 面向对象的方式
// 创建一个模板,用于创建对象(实例instance)
// 在JavaScript中创建对象的模板是构造函数
// 而在其他语言中创建对象的模板是类
function Student(name, subject, score) {
/*
当使用new一个构造函数的时候,会产生一个对象,并且this就会指向这个对象
给this增加一些属性就是给新创建的对象增加属性
*/
this.name = name;
this.subject = subject;
this.score = score;
this.printScore = function () {
console.log(this.name, this.subject, this.score);
}
}
var stu1 = new Student('zs', '语文', 90);
var stu2 = new Student('ls', '语文', 80);
stu1.printScore();
stu2.printScore();
</script>
创建对象的方式
<script>
// ************************************创建对象的方式有哪些?*************************
// 方法1: new Object()
var hero = new Object(); // 空对象
hero.blood = 100;
hero.name = '刘备';
hero.weapon = '剑';
hero.attack = function () {
console.log(this.weapon + ' 攻击敌人');
}
// 方法2: 对象字面量 如:var hero = {}; 空对象
var hero = {
blood: 100,
name: '刘备',
weapon: '剑',
attack: function () {
console.log(this.weapon + ' 攻击敌人');
}
}
hero.attack();
// 方法3: 工厂函数 创建多个对象 缺点:无法判断对象具体的数据类型
function createHero(name, blood, weapon) {
var o = new Object();
o.name = name;
o.blood = blood;
o.weapon = weapon;
o.attack = function () {
console.log(this.weapon + ' 攻击敌人');
}
return o;
}
var hero = createHero('刘备', 100, '剑');
var hero1 = createHero('关羽', 100, '刀');
</script>
通过工厂模式我们解决了创建多个相似对象代码冗余的问题,
但却没有解决对象识别的问题(即怎样知道一个对象的类型)。
构造函数
构造函数如何工作的呢?
当调用new Hero()
的时候:
- 会在内存中创建一个空对象
- 设置构造函数的this,让this指向刚刚创建好的对象
- 执行构造函数中的代码
- 返回对象
下面是具体的伪代码:
// 构造函数 -- 构造对象的函数:首字母大写
function Hero(name, blood, weapon) {
this.name = name;
this.blood = blood;
this.weapon = weapon;
this.attack = function () {
console.log(this.weapon + ' 攻击敌人');
}
}
var hero = new Hero('刘备', 100, '剑');
hero.attack();
// 第一点:无法使用typeof获取对象的具体类型
console.log(typeof hero);
var arr = new Array();
console.log(typeof arr);
// 第二点:使用 constructor 构造器 获取对象的具体类型 (不建议使用)
console.log(hero.constructor === Hero);
var arr = []; //new Array()
console.log(arr.constructor === Array);
// 第三点:使用 instanceof 判断某个对象是否是某个构造函数的实例/对象
console.log(hero instanceof Hero);
var arr = [];
console.log(arr instanceof Array);
静态成员和实例成员
<script>
function Hero(name, blood, weapon) {
//(位于构造函数内部)实例成员 / 对象成员 -- 跟对象相关的成员,将来使用对象的方式来调用
this.name = name;
this.blood = blood;
this.weapon = weapon;
this.attack = function() {
console.log(this.weapon + ' 攻击敌人');
}
}
// 静态成员(与对象无关) -- 直接给构造函数添加的成员
Hero.version = '1.0';
var hero = new Hero('刘备', 100, '剑');
hero.attack(); // 剑 攻击敌人
var hero1 = new Hero('关羽', 100, '刀');
hero1.attack(); // 刀 攻击敌人
// 静态成员不能使用对象的方式来调用
console.log(hero.version); // undefined
// 静态成员使用构造函数来调用
console.log(Hero.version); // 1.0
</script>
构造函数和实例对象的关系
使用构造函数的好处不仅仅在于代码的简洁性,更重要的是我们可以识别对象的具体类型了。
在每一个实例对象中同时有一个 constructor
属性,该属性指向创建该实例的构造函数:
console.log(p1.constructor === Person) // => true
console.log(p2.constructor === Person) // => true
console.log(p1.constructor === p2.constructor) // => true
对象的 constructor
属性最初是用来标识对象类型的,
但是,如果要检测对象的类型,还是使用 instanceof
操作符更可靠一些:
console.log(p1 instanceof Person) // => true
console.log(p2 instanceof Person) // => true
总结:
- 构造函数是根据具体的事物抽象出来的抽象模板
- 实例对象是根据抽象的构造函数模板得到的具体实例对象
- 每一个实例对象都具有一个
constructor
属性,指向创建该实例的构造函数- 注意:
constructor
是实例的属性的说法不严谨,具体后面的原型会讲到
- 注意:
- 可以通过实例的
constructor
属性判断实例和构造函数之间的关系- 注意:这种方式不严谨,推荐使用
instanceof
操作符,后面学原型会解释为什么
- 注意:这种方式不严谨,推荐使用
构造函数的问题
使用构造函数带来的最大的好处就是创建对象更方便了,但是其本身也存在一个浪费内存
的问题:
// 构造函数
function Student(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
// 多个对象,会存储多个sayHi方法
this.sayHi = function () {
console.log('大家好,我是' + this.name);
}
}
// 创建对象
var s1 = new Student('lilei', 18, '男');
var s2 = new Student('hmm', 18, '女');
s1.sayHi();
s2.sayHi();
// 内存地址不同
console.log(s1.sayHi === s2.sayHi); // false
解决方法:通过原型对象增加方法
<script>
// 每一个构造函数都有一个属性prototype(原型),它也是一个对象,称为:原型对象
function Student(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// console.dir(Student.prototype); // object
// 通过原型对象增加的方法:
Student.prototype.sayHi = function() {
console.log('大家好,我是' + this.name);
}
// 通过Student构造函数,创建的对象,可以访问Student.prototype中的成员
var s1 = new Student('lilei', 18, '男');
var s2 = new Student('hmm', 18, '女');
s1.sayHi(); // 大家好,我是lilei
s2.sayHi(); // 大家好,我是hmm
console.log(s1.sayHi === s2.sayHi); //true
</script>
第三章:原型
更好的解决方案: prototype
JavaScript 规定,每一个构造函数都有一个 prototype
属性,指向另一个对象。
这个对象的所有属性和方法,都会被构造函数的所拥有。
这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype
对象上。
<script>
// 每一个构造函数都有一个属性 原型 / 原型对象
function Student(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
// this.sayHi = function () {
// console.log('test');
// }
}
Student.prototype.sayHi = function() {
console.log('大家好,我是' + this.name);
}
// 通过Student构造函数,创建的对象,可以访问Student.prototype中的成员
var s1 = new Student('lilei', 18, '男');
var s2 = new Student('hmm', 18, '女');
s1.sayHi();
console.dir(s1);
// 当调用对象的属性或者方法的时候,先去找对象本身的属性/方法 ,如果对象没有该属性或者方法。此时去 调用原型中的属性/方法
// 如果对象本身没有该属性/方法,原型中也没有该属性或者方法,此时会报错
// s1.__proto__ 对象的__proto__ 等于 构造函数的Student.prototype
// __proto__属性是非标准的属性(各个浏览器的属性名可能不一样)
console.log(s1.__proto__ === Student.prototype); // true
console.dir(s1.__proto__); //
console.dir(Student.prototype);
// 在原型对象中有一个属性 constructor(构造函数)
// constructor作用:记录了创建该对象的构造函数
// 对象可以访问原型中的所以成员s1.constructor
console.log(s1.constructor === Student); // true
var arr = []; // 数组也是对象,而对象可以访问原型中的所以成员
// Array.prototype.constructor
console.log(arr.constructor === Array);
</script>
构造函数、实例、原型三者之间的关系
<script>
// 构造函数 原型对象 实例/对象s1.__proto__ 之间的关系
function Student(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
Student.prototype.sayHi = function () {
console.log('大家好,我是' + this.name);
}
// 通过Student构造函数,创建的对象,可以访问Student.prototype中的成员
var s1 = new Student('lilei', 18, '男');
var s2 = new Student('hmm', 18, '女');
// Student.prototype.constructor 指向了构造函数本身
</script>
任何函数都具有一个 prototype
属性,该属性是一个对象。
function F () {}
console.log(F.prototype) // => object
F.prototype.sayHi = function () {
console.log('hi!')
}
构造函数的 prototype
对象默认都有一个 constructor
属性,指向 prototype
对象所在函数。
console.log(F.prototype.constructor === F) // => true
通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype
对象的指针 __proto__
。
var instance = new F()
console.log(instance.__proto__ === F.prototype) // => true
`__proto__` 是非标准属性。
实例对象可以直接访问原型对象成员。
instance.sayHi() // => hi!
经验总结:
- 任何函数都具有一个
prototype
属性,该属性是一个对象 - 构造函数的
prototype
对象默认都有一个constructor
属性,指向prototype
对象所在函数 - 通过构造函数得到的实例对象内部会包含一个指向构造函数的
prototype
对象的指针__proto__
- 所有实例都直接或间接继承了原型对象的成员
属性成员的搜索原则:原型链
总结:
- 先在自己身上找,找到即返回
- 自己身上找不到,则沿着原型链向上查找,找到即返回
- 如果一直到原型链的末端还没有找到,则返回
undefined
属性查找的规则
<script>
// 属性查找规则
function Student(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
Student.prototype.sayHi = function () {
console.log('大家好,我是' + this.name);
}
// 构造函数的原型对象增加一个属性
Student.prototype.test = 'abc';
var s1 = new Student('lilei', 18, '男');
var s2 = new Student('hmm', 18, '女');
// 证明:s1对象的原型对象的原型对象 等价于 Object构造函数的原型对象
// console.log(s1.__proto__.__proto__ === Object.prototype); //true
// 证明:所有的对象都有toString()方法
console.log(s1.toString());
console.dir(s1);
console.dir(s1.__proto__);
console.dir(s1.__proto__.__proto__);
// 1、如何读取属性 先在对象本身查找test属性,如果没有找到的话,会去原型链上查找
// console.log(s1.name);
// console.log(s1.test);
// 2、如何设置属性
s1.name = 'xxx';
// test属性在原型对象上,而在设置属性的值的时候,不会搜索原型链而是直接给对象新增一个test属性
s1.test = '123xxx';
// 为s1对象新增一个address属性(只属于s1对象)
s1.address = '杭州'
console.log(s1.name); // 打印结果:xxx
console.log(s1.test); // 打印结果:123xxx (s1本身有test属性,使用本身的test)
console.log(s1.address); // 打印结果:杭州
console.log(s2.name); // 打印结果:hmm
console.log(s2.test); // 打印结果:abc (s2本身没有test属性,使用原型对象的test)
console.log(s2.address); // 打印结果:undefined(s2本身没有address属性且原型链上也没有,所以返回:undefined)
</script>
使用原型对象的注意点
-
当我们改变构造函数的
prototype
的时候,需要重新设置constructor属性原因如下: 改变原型对象的prototype属性,可以设置为一个新的对象,这个新的对象没有constructor属性 根据原型链查找,原型对象的原型对象有constructor属性, 而 原型对象中的成员 constructor =》指向 创建对象所使用的构造函数,指向:系统默认object 此时:对象.constructor属性 =》 指向:系统默认object (而不是我们想要的构造函数)
-
先去设置原型属性,再创建对象,才可以访问原型对象中的成员
修改原型对象之前
Student.prototype
构造函数的prototype
属性,是一个对象,称为:原型对象
原型对象的constructor
属性 =>指向 创建该对象的构造函数 Student
<script>
function Student(name, age) {
// 一般情况下,对象的属性在构造函数中来设置
this.name = name;
this.age = age;
}
var stu = new Student('ls', 18);
// 修改原型对象之前
console.dir(Student.prototype); // 打印结果如下:
// Object
// constructor: ƒ Student(name, age)
// __proto__: Object
</script>
修改原型对象之后
面试题:为什么改变了构造函数的prototype
属性之后,对象的constructor属性指向会发生改变?
因为:s1对象没有constructor
属性,根据原型链查找,原型对象的原型对象有constructor
属性,
而 原型对象中的成员 constructor
=》指向 创建对象所使用的构造函数,指向
:系统默认object
<script>
function Student(name, age) {
// 一般情况下,对象的属性在构造函数中来设置
this.name = name;
this.age = age;
}
// 1. 对象的方法在构造函数的原型对象中来设置,可以重新改变原型对象的prototype属性,设置为一个新的对象。 但是这么写会有一个问题:当我们改变构造函数的prototype的时候,需要重新设置constructor属性
Student.prototype = {
sayHi: function () {
console.log('sayHi');
},
eat: function () {
console.log('eat');
}
}
// 修改原型对象之后且没有重新设置constructor属性,此时指向 object
// 因为s1对象没有constructor属性,根据原型链查找,原型对象的原型对象有constructor属性,
// 而 原型对象中的成员 constructor =》指向 创建对象所使用的构造函数,指向:系统默认object
var s1 = new Student('zs', 18);
console.log(s1.constructor); // 通过对象访问原型对象的成员(因为存在原型链)
// 结果:ƒ Object() { [native code] }
</script>
如果修改了prototype
属性,就不能通过对象.constructor
,找到我们想要的构造函数,如何解决呢?
解决方法:
我们为了保持 constructor
的指向正确,建议的写法是:
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype = {
constructor: Person, // => 手动将 constructor 指向正确的构造函数
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
}
}
var s1 = new Person('zs', 18);
console.log(s1.constructor); // 打印结果如下:此时 s1.constructor 指向 该对象的构造函数 Person
// ƒ Person(name, age) {
// this.name = name
// this.age = age
// }
原型对象使用建议
- 私有成员(一般就是非函数成员)放到构造函数中
- 共享成员(一般就是函数)放到原型对象中
- 如果重置了
prototype
记得修正constructor
的指向
原型对象的应用:扩展内置对象
注意:数组 或者String 中的prototype是不可以修改的,下面的写法语法上不允许,系统报错。
<script>
var array = [5, 4, 1, 8];
// 给数组的原型对象新增方法:
Array.prototype.getSum = function() {
// 求数组中所有偶数的和
var sum = 0;
for (var i = 0; i < this.length; i++) {
if (this[i] % 2 === 0) {
sum += this[i];
}
}
return sum;
}
console.log(array.getSum());
// 注意:数组 或者String 中的prototype是不可以修改的,下面的写法语法上不允许,系统报错。
// Array.prototype = {
// getSum: function () {
// // 求数组中所有偶数的和
// var sum = 0;
// for (var i = 0; i < this.length; i++) {
// if (this[i] % 2 === 0) {
// sum += this[i];
// }
// }
// return sum;
// }
// }
</script>
第四章:继承
对象之间的继承
对象之间的继承不是真正的继承,称为
对象的拷贝
。真正的继承是类型和类型之间的关系
<script>
// ...方法1:复制对象的成员给另一个对象...
var wjl = {
name: '王健林',
money: 10000000,
cars: ['玛莎拉蒂', '特斯拉'],
houses: ['别墅', '大别墅'],
play: function () {
console.log('打高尔夫');
}
}
var wsc = {
name: '王思聪'
}
for (var key in wjl) {
// 不给wsc复制同名的属性(判断wsc[key]有没有这个属性,如:name)
if (wsc[key]) {
continue; // 不执行这个属性循环,继续下一个循环
}
wsc[key] = wjl[key];
}
console.dir(wsc); // 此时:wsc对象就有wjl(除name属性之外的所有属性和方法)
// ..............方法2:对象的拷贝...........................
// 复制对象的成员给另一个对象; 将parent对象复制给child对象
var wjl = {
name: '王健林',
money: 10000000,
cars: ['玛莎拉蒂', '特斯拉'],
houses: ['别墅', '大别墅'],
play: function() {
console.log('打高尔夫');
}
}
var wsc = {
name: '王思聪'
}
// 函数封装
function extend(parent, child) {
for (var key in parent) {
// 不给wsc复制同名的属性
if (child[key]) {
continue; // 不执行这个属性循环,继续下一个循环
}
child[key] = parent[key];
}
}
extend(wjl, wsc);
console.dir(wsc);
</script>
call和bind的使用
常规方法:this指向
// .............常规方法:求两个数的和..............
function fn(x, y) {
console.log(this); // 函数中的this指向函数的调用者,window
console.log(x + y); // 11
}
window.fn(5, 6);
call方法:
- call() 改变函数中的this,直接调用函数
- call的第一个参数:fn的this指向谁
// ............call方法:............
// call() 改变函数中的this,直接调用函数
// call的第一个参数:fn的this指向谁
function fn1(x, y) {
console.log(this); // 此时this指向对象w {name: "zy"}
console.log(x + y); // 5
}
var w = {
name: 'zy'
};
fn1.call(w, 2, 3);
bind方法:
- bind() 改变函数的this,并且返回一个新的函数 (但是bind不调用函数)
- bind的第一个参数:fn的this指向谁
// ..............bind方法:..............
// bind() 改变函数的this,并且返回一个新的函数 (但是bind不调用函数)
// bind的第一个参数:fn的this指向谁
function fn(x, y) {
console.log(this); // 此时this指向对象o {name: "zs"}
console.log(x + y); // 7
}
var o = {
name: 'zs'
};
// 此时this指向对象o
fn.bind(o, 5, 2)(); // 返回一个新的函数 (不调用函数);后面的()表示自调用
构造函数的属性继承:借用构造函数
<script>
// 借用构造函数:可以解决原型继承的问题 => 无法设置构造函数的参数
// 父类型
function Person(name, age, sex) {
this.name = name; // 为Student学生对象添加name属性
this.age = age; // 为Student学生对象添加age属性
this.sex = sex; // 为Student学生对象添加sex属性
// this.sayHi
};
// 借用构造函数,无法实现继承父类型的方法
Person.prototype.sayHi = function () {
console.log(this.name);
};
// 子类型
function Student(name, age, sex, score) {
// 借用构造函数继承属性成员
Person.call(this, name, age, sex); // 此时的this是Student学生对象
this.score = score;
};
var s1 = new Student('zs', 18, '男', 100);
console.dir(s1);
// 打印结果如下:Student里面就包含:父类中的属性,
// 如:name、age、sex (此时里面没有sayHi方法,所以:借用构造函数,无法实现继承父类型的方法)
// Student
// age: 18
// name: "zs"
// score: 100
// sex: "男"
</script>
组合继承
<script>
// 组合继承:借用构造函数 + 原型继承
// 1.借用构造函数存在的问题:只能够继承父类型的属性,无法继承父类型的方法
// 2.原型继承:设置原型为父类型的对象;可以实现继承父类型的方法
// 父类型 -- 人类对象
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 所有子类型中都要有sayHi方法:子类型中共享的方法可以定义到父类型上
Person.prototype.sayHi = function () {
console.log('大家好,我是' + this.name);
}
// 子类型 -- 学生对象
function Student(name, age, sex, score) {
// 我们想让Student也具有父类型中的name, age, sex这些属性
// 所以我们可以通过借用构造函数:将Person当成普通的函数
// 通过call进行调用,改变this的指向;将Person中的this指向Student对象
// 然后把相应的属性传给Student对象
Person.call(this, name, age, sex);
this.score = score;
}
// 通过原型让子类型继承父类型中的方法
Student.prototype = new Person();
Student.prototype.constructor = Student;
// 学生特有的方法
Student.prototype.exam = function () {
console.log('考试');
}
var s1 = new Student('zs', 18, '男', 100);
console.dir(s1); // 只要学生有exam方法
var p1 = new Person('ls', 18, '男');
console.dir(p1); // 里面没有学生的exam方法
// 子类型 -- 老师对象
function Teacher(name, age, sex, salary) {
// 借用构造函数
Person.call(this, name, age, sex);
this.salary = salary;
}
// 通过原型让子类型继承父类型中的方法
Teacher.prototype = new Person();
Teacher.prototype.constructor = Teacher;
var t1 = new Teacher('ww', 30, '男', 100000);
console.dir(t1); // 里面没有学生的exam方法
t1.sayHi(); // 调用父类的sayHi方法
</script>
原型分析图
构造函数的原型方法继承:拷贝继承(for-in)
function Person (name, age) {
this.type = 'human'
this.name = name
this.age = age
}
Person.prototype.sayName = function () {
console.log('hello ' + this.name)
}
function Student (name, age) {
Person.call(this, name, age)
}
// 原型对象拷贝继承原型对象成员
for(var key in Person.prototype) {
Student.prototype[key] = Person.prototype[key]
}
var s1 = Student('张三', 18)
s1.sayName() // => hello 张三