1、面向对象概述
(1)面向过程与面向对象
以完成一件事来说明什么是面向过程与面向对象。
面向过程的解决办法:注重的是具体的步骤,只有按照步骤一步一步的执行,才能够完成这件事情。
面向对象的解决办法:注重的是一个个对象,这些对象各司其职,我们只要发号施令,即可指挥这些对象帮我们完成任务。
总结:
对于面向过程思想,我们扮演的是执行者,凡事都要靠自己完成。
对于面向对象思想,我们扮演的是指挥官,只要找到相应的对象,让它们帮我们做具体的事情即可。
(2)面向对象的特征
封装:指的是隐藏内部的实现细节,只对外开放操作接口。
继承:是指一个对象继承另一个对象的成员,从而在不改变另一个对象的前提下进行扩展。
多态:指的是同一个操作作用于不同的对象,会产生不同的执行结果。
2、自定义对象
(1)通过“{ }”语法实现
语法:对象的定义是通过“{ }”语法实现的。
组成:对象以对象成员(属性和方法)构成,多个成员之间使用逗号分隔。
成员:对象的成员以键值对的形式存放在{}中。
<script>
var o1 = {}; // 定义空对象
var o2 = {name: 'Jim'}; // 定义含有name属性的对象
var o3 = {name: 'Jim', age: 19, gender: '男'}; // 定义含有3个属性的对象
</script>
<script>
var o4 = {
name: 'Jim', // 成员属性o4.name
age: 19, // 成员属性o4.age
gender: '男', // 成员属性o4.gender
sayHello: function() { // 成员方法o4.sayHello()
console.log('你好');
}
};
</script>
“{ }”语法又称为对象的字面量语法,所谓字面量是指在源代码中直接书写的一个表示数据和类型的量,如123(数值型)、‘123’(字符型)、[123](数组)都是字面量。
(2)使用构造函数实现
构造函数如何创建对象:new 构造函数名()。
什么是实例化与实例:人们习惯将使用new关键字创建对象的过程称为实例化,实例化后得到的对象称为构造函数的实例。
思考:如何自定义构造函数?
构造函数的命名推荐采用帕斯卡命名规则,即所有的单词首字母大写。
在构造函数内部,使用this来表示刚刚创建的对象。
<script>
function Person(name, age) {
this.name = name; // 添加name属性
this.age = age; // 添加age属性
this.sayHello = function () { // 添加sayHello()方法
console.log('Hello, my name is ' + this.name);
};
}
var p1 = new Person('Jack', 18);
var p2 = new Person('Alice', 19);
console.log(p1); // 输出结果:Person {name: "Jack", age: 18}
console.log(p2); // 输出结果:Person {name: "Alice", age: 19}
p1.sayHello(); // 输出结果:Hello, my name is Jack
console.log(p1.constructor); // 输出结果:function Person(name, age) ……
</script>
“对象.constructor”属性指向了该对象的构造函数。
通过console.log()输出时,[native code]表示该函数的代码是内置的。
思考:如何自定义构造函数?
构造函数的命名推荐采用帕斯卡命名规则,即所有的单词首字母大写。
在构造函数内部,使用this来表示刚刚创建的对象。
注意:
在学习JavaScript时,初学者经常会对一些相近的名词感到困惑,如函数、
方法、构造函数、构造方法、构造器等。
实际上,它们都可以统称为函数,只不过在不同使用场景下的称呼不同。根据习惯,对象中定义的函数称为对象的方法。
而对于构造函数,也有一部分人习惯将其称为构造方法或构造器,我们只需明白这些称呼所指的是同一个事物即可。
(3)访问对象成员
语法:对象.成员。
<script>
var o5 = {}; // 创建一个空对象
o5.name = 'Jack'; // 为对象增加属性
o5.introduce = function () { // 为对象增加方法
alert('My name is ' + this.name); // 在方法中使用this代表当前对象
};
alert(o5.name); // 访问name属性,输出结果:Jack
o5.introduce(); // 调用introduce()方法,输出结果:My name is Jack
</script>
可变成员名语法:对象[变量名] = 值。
<script>
var o6 = {}; // 创建一个空对象
var key = 'id'; // 通过变量保存要操作的属性名
o6[key] = 123; // 相当于“o6['id'] = 123”或“o6.id = 123”
</script>
(4)对象成员遍历
语法:for…in。
<script>
var obj = {name: 'Tom', age: 16};
for (var k in obj) {//变量k保存了每个对象成员的名称。
console.log(k + '-' + obj[k]);
//obj[k]访问成员属性的值。obj[k]()调用成员方法。
}
</script>
(5)判断对象成员是否存在
当需要判断一个对象中的某个成员是否存在时,可以使用in运算符。
当对象的成员存在时返回true,不存在时返回false。
<script>
var obj = {name: 'Tom', age: 16};
console.log('name' in obj); // 输出结果:true
console.log('gender' in obj); // 输出结果:false
</script>
(6)深拷贝与浅拷贝
拷贝(copy):是指将一个目标数据复制一份,形成两个个体。
深拷贝:参与拷贝的两个目标,改变其中一个目标的值,不会影响另一个目标的值。
浅拷贝:参与拷贝的两个目标,一个目标的值改变,另一个目标的值也会随之改变。
<script>
var p1 = {name: 'Jim', age: 19};
var p2 = p1;
p2.name = 'Tom';
console.log(p1); // 输出结果:Object {name: "Tom", age: 19}
console.log(p2); // 输出结果:Object {name: "Tom", age: 19}
console.log(p1 === p2); // 输出结果:true
</script>
(7)class
class关键字的作用:用来定义一个类。
特点:在类中可以定义constructor构造方法。
<script>
// 定义类
class Person {
constructor (name, age, gender) { // 构造方法
this.name = name; // 添加name属性
this.age = age; // 添加age属性
this.gender = gender; // 添加gender属性
}
introduce() { // 定义introduce()方法
console.log('我是' + this.name + ',今年' + this.age + '岁。');
}
}
// 实例化时会自动调用constructor()构造方法
var p = new Person('Jim', 19, '男');
p.introduce(); // 输出结果:我是Jim,今年19岁。
</script>
class语法本质上是语法糖,只是方便用户使用而设计的,不使用该语法同样可以达到相同的效果,如前面学过的构造函数。为了避免用户的浏览器不支持此语法,因此不推荐使用此方式。
(8)私有成员
概念:在构造函数中,使用var关键字定义的变量称为私有成员。
特点:在实例对象后无法通过“对象.成员”的方式进行访问,但是私有成员可以在对象的成员方法中访问。
特性:私有成员name体现了面向对象的封装性。
<script>
function Person() {
var name = 'Jim';
this.getName = function () {
return name;
};
}
var p = new Person(); // 创建实例对象p
console.log(p.name); // 访问私有成员,输出结果:undefined
p.getName(); // 访问对外开放的成员,输出结果:Jim
</script>
(9)静态成员、实例成员
静态成员:指由构造函数所使用的成员。
实例成员:指由构造函数创建的对象所使用的成员。
<script>
function Person(name) {
// // 为Person对象添加实例成员
this.name = name;
this.sayHello = function() {
console.log(this.name);
};
}
// 为Person对象添加静态成员
Person.age = 123;
Person.sayGood = function() {
console.log(this.age);
};
// 构造函数使用的成员是静态成员
console.log(Person.age); // 使用静态属性age,输出结果:123
Person.sayGood(); // 使用静态方法sayGood(),输出结果:123
// 由构造函数创建的对象使用的成员是实例成员
var p = new Person('Tom');
console.log(p.name); // 使用实例属性name,输出结果:Tom
p.sayHello(); // 使用实例方法sayHello(),输出结果:Tom
</script>
(10)构造函数中的return关键字
构造函数的本质是函数,因此构造函数中也可以使用return关键字。
构造函数在使用时与普通函数有一定的区别:
若用return返回一个数组或对象等引用类型数据,则构造函数直接返回该数据,而不会返回原来创建的对象。
若返回的是基本类型数据,则返回的数据无效,依然会返回原来创建的对象。
<script>
// 返回基本类型数据
function Person() {
// 若返回的是基本类型数据,则返回的数据无效,依然会返回原来创建的对象
obj = this;
return 123;
}
var p = new Person();
console.log(p +'----'+ obj);//[object Object]----[object Object]
console.log(p === obj); // true
</script>
<script>
// 返回引用类型数据
// 若用return返回一个数组或对象等引用类型数据,则构造函数直接返回该数据,而不会返回原来创建的对象。
function Person() {
objs = this;
return [];
}
var p = new Person();
console.log(p +'----'+ obj);
console.log(p === obj); // false
</script>
(11)函数中的this指向
this的特点:根据函数不同的调用方式,函数中的this指向会发生改变。
分析this指向
在JavaScript中,函数内的this指向通常与以下3种情况有关。
a、new关键字将函数作为构造函数调用时,构造函数内部的this指向新创建的对象。
b、直接通过函数名调用函数时,this指向全局对象(浏览器中表示window对象)。
c、如果将函数作为对象的方法调用,this将会指向该对象。
<script>
function foo() {
return this;
}
var o = {name: 'Jim', func: foo};
// // 直接通过函数名调用函数时,this指向全局对象(浏览器中表示window对象)
console.log(foo() === window); // 输出结果:true
// 如果将函数作为对象的方法调用,this将会指向该对象
console.log(o.func() === o); // 输出结果:true
</script>
更改this指向
<script>
function method() {
console.log(this.name);
}
// allay\call的第一个参数 表示将this指向哪个对象,因此this.name访问到传入对象的name属性。
method.apply({name: '张三'}); // 输出结果:张三
method.call({name: '李四'}); // 输出结果:李四
</script>
<script>
function method(a, b) {
console.log(a + b);
}
// apply的第二个参数 表示调用函数时传入的参数,通过数组的形式传递
method.apply({}, ['1', '2']); // 数组方式传参,输出结果:12
// call()则使用第2~N 个参数来表示调用函数时传入的函数
method.call({}, '3', '4'); // 参数方式传参,输出结果:34
</script>
<script>
function method(a, b) {
console.log(this.name + a + b);
}
var name = '张三';
var test = method.bind({name: '李四'}, '3', '4');
method('1', '2'); // 输出结果:张三12
test(); // 输出结果:李四34
</script>
<script>
let callStr = "这是window的str";
let callObj = {callStr: "这是obj对象的str"};
function callFun(name, age) {
this.name = name;
this.age = age;
console.log(this, this.callStr);
}
var name = 'ww'
callFun.call(callObj);
callFun.apply(callObj);
callFun.bind(callObj)();
// 以上三条都输出: {callStr: "这是obj对象的str", name: undefined, age: undefined} "这是obj对象的str"
callFun.call(callObj, "张三", "23");
callFun.apply(callObj, ["张三", "23"]);
callFun.bind(callObj, "张三", "23")();
// 以上三条都输出 {callStr: "这是obj对象的str", name: "张三", age: 23} "这是obj对象的str"
</script>
总结
a、apply()和call()方法的第1个参数表示将this指向哪个对象。
b、apply()方法的第2个参数表示调用函数时传入的参数,通过数组的形式传递; call()方法的第2~N个参数来表示调用函数时传入的函数。
c、bind()方法的作用:用于在调用函数前指定this的含义,实现提前绑定的效果。
d、bind()方法的第1个参数:将this指向哪个对象。
e、bind()方法的第2~N个参数:表示调用函数时传入的函数。
3、常用内置对象
(1)String对象
String对象提供了一些用于对字符串进行处理的属性和方法。
<script>
var str = 'HelloWorld';
console.log(str.length); // 获取字符串长度,返回结果:10
console.log(str.charAt(5)); // 获取索引位置为5的字符,返回结果:W
console.log(str.indexOf('o')); // 获取“o”在字符串中首次出现的位置,返回结果:4
console.log(str.lastIndexOf('o')); // 获取“o”在字符串中最后出现的位置,返回结果:6
console.log(str.substring(5)); // 截取从位置5开始到最后的内容,返回结果:World
console.log(str.substring(5, 7)); // 截取从位置5开始到位置7范围内的内容,返回结果:Wo
console.log(str.substr(5)); // 截取从位置5开始到最后的内容,返回结果:World
console.log(str.substr(5, 2)); // 截取从位置5开始的后面2个字符,返回结果:Wo
console.log(str.toLowerCase()); // 将字符串转换为小写,返回结果:helloworld
console.log(str.toUpperCase()); // 将字符串转换为大写,返回结果:HELLOWORLD
console.log(str.split('l')); // 使用“l”切割字符串,返回结果:["He", "", "oWor", "d"]
console.log(str.split('l', 3)); // 限制最多切割3次,返回结果:["He", "", "oWor"]
console.log(str.replace('World', 'JavaScript')); // 替换字符串,返回结果:"HelloJavaScript"
</script>
对字符串进行操作时,处理结果是通过方法的返回值直接返回的,并不会改变String对象本身保存的字符串内容。在这些方法的参数中,位置是一个索引值,从0开始计算,第一个字符的索引值是0,最后一个字符的索引值是字符串的长度减1。
<script>
var name = 'Administrator';
if (name.length < 3 || name.length > 10) {
alert('用户名长度必须在3~10之间。');
}
// indexof 在字符串中首次出现的位置
if (name.toLowerCase().indexOf('admin') !== -1) {
alert('用户名中不能包含敏感词:admin。');
}
</script>
(2)Number对象
Number对象用于处理整数、浮点数等数值。
<script>
var num = 12345.6789;
console.log(num.toFixed()); // 四舍五入,不包括小数部分,返回结果:12346
console.log(num.toFixed(1)); // 四舍五入,保留1位小数,返回结果:12345.7
console.log(num.toFixed(6)); // 用0填充不足的小数位,返回结果:12345.678900
console.log(Number.MAX_VALUE); // 获取最大值,返回结果:1.7976931348623157e+308
console.log(Number.MIN_VALUE); // 获取最小正值,返回结果:5e-324
</script>
MAX_VALUE、MIN_VALUE是Number的静态成员,直接通过构造函数Number进行访问,而非Number的实例。
(3)Math对象
Math对象用于对数值进行数学运算,与其他对象不同的是,该对象不是一个构造函数,不需要实例化就能使用。
<script>
var num = 10.88;
console.log(Math.ceil(num)); // 向上取整,返回结果:11
console.log(Math.round(num)); // 四舍五入,返回结果:11
console.log(Math.random()); // 获取随机数,返回结果:0.3938305016297685(每次结果不同)
console.log(Math.abs(-25)); // 获取绝对值,返回结果:25
console.log(Math.abs('-25')); // 获取绝对值,返回结果:25
console.log(Math.max(5, 7, 9, 8)); // 获取最大值,返回结果:9
console.log(Math.min(6, 2, 5, 3)); // 获取最小值,返回结果:2
</script>
<script>
//Math.random() * (n - m) + m,表示生成大于或等于m且小于n的随机值
console.log(Math.random() * (3 - 1) + 1); // 1 <= 返回结果 < 3
console.log(Math.random() * (20 - 10) + 10); // 20 <= 返回结果 < 20
console.log(Math.random() * (99 - 88) + 88); // 88 <= 返回结果 < 99
</script>
<script>
function randomNum(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
console.log(randomNum(1, 3)); // 最小值1,最大值3
</script>
(4)Date对象
Date对象用于处理日期和时间。
<script>
//根据Date对象获取时间日期
var date = new Date(); // 基于当前时间创建Date对象
console.log(date.toString()); // 示例结果:Fri Oct 06 2017 11:53:04 GMT+0800 (中国标准时间)
console.log(date.getFullYear()); // 示例结果:2017
console.log(date.getMonth()); // 示例结果:9
console.log(date.getDate()); // 示例结果:6
</script>
<script>
//根据Date对象指定一个日期
// 方式1:分别传入年、月、日、时、分、秒(月的范围是0~11,即真实月份-1)
var date1 = new Date(2017, 9, 1, 11, 53, 4);
console.log(date1.toString()); // 返回结果:Sun Oct 01 2017 11:53:04 GMT+0800 (中国标准时间)
// 方式2:通过字符串传入日期和时间
var date2 = new Date('2017-10-01 11:53:04');
console.log(date2.toString()); // 返回结果:Sun Oct 01 2017 11:53:04 GMT+0800 (中国标准时间)
</script>
<script>
//处理设置的日期不合理的情况,如将月份设为-1表示去年12月,月份为12表示明年1月。
console.log(new Date('2017')); // Sun Jan 01 2017 08:00:00 GMT+0800 (中国标准时间)
console.log(new Date(2017, 9)); // Sun Oct 01 2017 00:00:00 GMT+0800 (中国标准时间)
console.log(new Date(2017, -1)); // Thu Dec 01 2016 00:00:00 GMT+0800 (中国标准时间)
console.log(new Date(2017, 12)); // Mon Jan 01 2018 00:00:00 GMT+0800 (中国标准时间)
console.log(new Date(2017, 0, 0)); // Sat Dec 31 2016 00:00:00 GMT+0800 (中国标准时间)
</script>
4、构造函数、原型、原型链
(1)原型
在JavaScript中,每定义一个函数,就随之有一个对象存在,函数通过prototype属性指向该对象。这个对象称之为原型对象,简称原型。
<script>
function Person() {} // 定义函数
console.log(typeof Person.prototype); // 输出结果:object
</script>
Person函数的prototype属性指向的对象,就是Person的原型对象。
在利用构造函数创建对象时,每个对象都默认与这个原型对象连接,连接后就可以访问到原型对象中的属性和方法。
<script>
function Person(name) {
this.name = name;
}
Person.prototype.introduce = function() {console.log(2)};
var p1 = new Person('Jim');
var p2 = new Person('Alice');
console.log(p1.introduce());
console.log(p1.name); // 输出结果:function () {}
console.log(p1.introduce() === p2.introduce()); // 输出结果:true
</script>
(2)原型链
概念:在JavaScript中,对象有原型对象,原型对象也有原型对象,这就形成了一个链式结构,简称原型链。
a、constructor属性:对象的构造函数
在原型对象中,存在一个constructor属性,指向该对象的构造函数。
<script>
function Person() {}
Person.prototype.constructor === Person; // 返回结果:true
</script>
<script>
function Person() {}
new Person().constructor === Person; // 返回结果:true
</script>
<script>
function Person() {}
new Person().constructor.prototype === Person.prototype; // 返回结果:true
</script>
注意:
对象可以通过constructor属性访问构造函数。
构造函数可以通过prototype属性访问原型对象。
因此,对象.constructor.prototype即可访问对象的原型对象。
b、函数的构造函数
由于函数本质上就是对象,所以函数也有构造函数。
<script>
function Person() {}
Person.constructor.toString(); // 返回结果:function Function() { [native code] }
Person.constructor === Function; // 返回结果:true
String.constructor === Function; // 返回结果:true
Number.constructor === Function; // 返回结果:true
Object.constructor === Function; // 返回结果:true
Function.constructor === Function; // 返回结果:true
</script>
总结:
在JavaScript中,自定义函数以及String、Number、Object等内置构造函数的构造函数都是Function函数。
Function函数的构造函数是Function自身。
用户还可以通过实例化Function构造函数的方式来创建函数。该构造函数的参数数量是不固定的,最后一个参数表示用字符串保存的新创建函数的函数体,前面的参数(数量不固定)表示新创建函数的参数名称。
<script>
// 1、创建函数
// new Function('参数1', '参数2', …… '参数N', '函数体');
var func = new Function('a', 'b', 'return a + b;');
// 调用函数
console.log(func(100, 200)); // 输出结果:300
</script>
<script>
// 相当于
var func = function(a, b) {
return a + b;
};
</script>
c、原型对象的原型对象
已知1:对象.constructor.prototype,可访问对象的原型对象。
已知2:构造函数的prototype属性指向原型对象,原型对象的constructor属性又指回了构造函数,这就构成了一个循环。
结论:通过这种方式无法访问到原型对象的原型对象。
解决办法:浏览器为对象增加了一个新的属性__proto__,方法查看对象的原型。
适用范围:一些新版的浏览器,如火狐、Chrome等。
属性特点:由于其不是JavaScript原有属性,因此前后加两个下划线进行区分。
查看位置:在开发人员工具中方便地查看对象的原型。
<script>
function Person() {}
new Person().__proto__ === Person.prototype; // 返回结果:true
</script>
<script>
// 构造函数的object的原型对象
Person.prototype.__proto__ === Object.prototype; // 返回结果:true
</script>
<script>
// 继续访问 Object.prototype的原型对象 结果为null
Object.prototype.__proto__; // 返回结果:null
// 构造函数 object 的原型对象是构造函数Function的原型对象
Object.__proto__ === Function.prototype; // 返回结果:true
</script>
构造函数Person的原型对象的原型对象,是构造函数Object的原型对象。
构造函数Object的原型对象的原型对象是null。
构造函数Object的原型对象是构造函数Function的原型对象。
原型链的结构
自定义函数,以及Object、String、Number等内置函数,都是由Function函数创建的,Function函数是由Function函数自身创建的。
每个构造函数都有一个原型对象,构造函数通过prototype属性指向原型对象,原型对象通过constructor属性指向构造函数。
由构造函数创建的实例对象,继承自构造函数的原型对象。通过实例对象的__proto__属性可以直接访问原型对象。
构造函数的原型对象,继承自Object的原型对象,而Object的原型对象的__proto__属性为null。
注意:
在进行原型操作时,“对象.constructor.prototype”访问到的是该对象当前继承的原型对象的构造函数的原型对象,并不一定是实际构造函数的原型对象。
<script>
function Person() {}
function Func() {}
// 在更改了构造函数Person的prototype属性后,新创建的对象p1继承的原型对象是构造函数Func的实例对象,
// 因此通过p1.constructor访问到的是构造函数Func,而不是p1的实际构造函数Person。
Person.prototype = new Func();
var p1 = new Person();
p1.constructor === Func; // 返回结果:true
p1.constructor.prototype === Func.prototype; // 返回结果:true
// 使用 p1.__proto__ 访问到的才是实际构造函数 person的原型对象
p1.__proto__ === Person.prototype; // 返回结果:true
</script>
(3)instanceof运算符
作用:检测一个对象的原型链中是否含有某个构造函数的prototype属性所表示的对象。
返回值:布尔类型,存在返回true,否则返回false。
<script>
// 示例1:简单应用
function Person() {}
var p1 = new Person();
console.log(p1 instanceof Person); // 输出结果:true
</script>
<script>
// 示例2:更改构造函数的prototype属性
function Person() {}
function Func() {}
var p1 = new Person();
Person.prototype = new Func();
var p2 = new Person();
console.log(p1 instanceof Person); // 输出结果:false
console.log(p2 instanceof Person); // 输出结果:true
</script>
<script>
// 示例3:让当前Person.prototype在p1的原型链上
p1.__proto__.__proto__ = Person.prototype;
console.log(p1 instanceof Person); // 输出结果:true
</script>
5、继承的实现
在JavaScript中,继承是在已有对象的基础上进行扩展,增加一些新的功能,得到一个新的对象。
(1)利用原型对象实现继承
<script>
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log('你好,我是' + this.name);
}
var p1 = new Person('Jim');
var p2 = new Person('Tom');
p1.sayHello(); // 输出结果:你好,我是Jim
p2.sayHello(); // 输出结果:你好,我是 Tom
</script>
(2)替换原型对象实现继承
<script>
function Person(name) {this.name = name;} // 构造函数Person原本有一个原型对象prototype
Person.prototype.sayHello = function () {
console.log('你好,我是' + this.name);
}
var p = new Person('lucy');
Person.prototype = { // 将构造函数的prototype属性指向一个新的对象
sayHello: function () { // 在新的对象中定义一个sayHello()方法用于测试
console.log('你好,我是新对象-');
}
}
p.sayHello(); // 输出结果:你好,我是新对象
</script>
<script>
function Person() {}
Person.prototype.sayHello = function() {
console.log('原来的对象');
}
var p1 = new Person();
Person.prototype = {
sayHello: function(){
console.log('替换后的对象');
}
}
var p2 = new Person();
p1.sayHello(); // 输出结果:原来的对象
p2.sayHello(); // 输出结果:替换后的对象
</script>
在基于构造函数创建对象时,代码应写在替换原型对象之后,否则创建的对象仍然会继承原来的原型对象。
(3)利用Object.create()实现继承
<script>
var obj = {
sayHello: function(){
console.log('我是一个带有sayHello方法的对象');
}
};
var newObj = Object.create(obj);
console.log(newObj)
newObj.sayHello(); // 输出结果:我是一个带有sayHello方法的对象
newObj.__proto__ === obj; // 返回结果:true
//将obj作为newObj对象的原型
</script>
(4)混入继承
混入就是将一个对象的成员加入到另一个对象中,实现对象功能的扩展。
实现混入继承最简单的方法就是将一个对象的成员赋值给另一个对象。
<script>
var o1 = {};
var o2 = {name: 'Jim'};
o1.name = o2.name; // o1继承o2的name属性
console.log(o1.name); // 输出结果:Jim
</script>
问题:当对象的成员比较多时,如果为每个成员都进行赋值操作,会非常麻烦。
答案:编写一个函数专门实现对象成员的赋值,函数通常命名为mix(混合)或extend(扩展) 。
<script>
// 编写extend函数
function extend(o1, o2) {
for (var k in o2) {
o1[k] = o2[k];
}
}
// 测试extend函数
var o1 = {name: 'Jim'};
var o2 = {age: 16, gender: 'male'};
extend(o1, o2); // 将o2的成员添加给o1
console.log(o1.name); // 输出结果:Jim
console.log(o1.age); // 输出结果:16
</script>
<script>
function Person(options) {
// 调用前面编写的extend(),将传入的options对象的成员添加到实例对象中
extend(this, options);
}
Person.fn = Person.prototype; // 将prototype属性简化为fn方便代码书写
Person.fn.extend = function(obj) {
extend(this, obj); // 此处的this相当于Peron.prototype
};
Person.fn.extend({
sayHello: function() {
console.log('你好,我是' + (this.name || '无名'));
}
});
var p1 = new Person();
var p2 = new Person({name: '张三', age:16});
p1.sayHello(); // 输出结果:你好,我是无名
p2.sayHello(); // 输出结果:你好,我是张三
</script>
混入式继承和原型继承还可以组合在一起使用,实现以对象的方式传递参数,或以对象的方式扩展原型对象的成员。
6、熟悉错误的处理,掌握如何在浏览器中调试JavaScript程序
(1)错误处理
错误处理:try…catch语句可对错误对象进行捕获,捕获后可以查看错误信息。
<script>
function foo1() {
foo2();
console.log('foo1');
}
function foo2() {
var o = {};
o.func(); // 发生错误
}
</script>
<script>
try {
foo1();
} catch(e) {
console.log('test');
}
</script>
注意:
如果try中有多行代码,只要其中一行出现错误,后面的代码都不会执行;如果错误已经被处理,则catch后面的代码会继续执行。由此可见,编写在try中的代码量应尽可能的少,从而避免错误发生时造成的影响。
在以Java为代表的编程语言中,引入了异常(Exception)的概念,利用try…catch进行异常处理。JavaScript错误处理的设计思想与之类似,因此也可以将JavaScript中的try…catch称为异常处理。
(2)错误对象
错误对象:在发生错误时,错误出现的位置、错误的类型,错误信息等数据,都会被封装起来,以一个对象的形式传递给catch语句,通过catch(e)的方式来接收,其中e是错误对象的变量名。
错误对象会在函数之间传递。
当try中的代码调用了其他函数时,如果在其他函数中出现了错误,且没有使用try…catch处理时,程序就会停下来。
将错误传递到调用当前函数的上一层函数,如果上一层函数仍然没有处理,则继续向上传递。
手动抛出错误对象:除了在JavaScript程序出现错误时自动抛出错误对象,用户也可以使用throw关键字手动抛出错误对象。
<script>
try {
var e1 = new Error('错误信息'); // 创建错误对象
throw e1; // 抛出错误对象
//也可以与上一行合并为:
// throw new Error('错误信息');
} catch (e) {
console.log(e.message); // 输出结果:错误信息
console.log(e1 === e); // 判断e1和e是否为同一个对象,输出结果:true
}
</script>