JavaScript面向对象(二)
一、原型链
1、访问对象的原型对象:每个对象都有一个__proto__属性,这个属性指向了对象的原型对象
function Person() {}
var p1 = new Person();
console.log(p1.__proto__ === Person.prototype); // 输出结果:true
实例对象与原型对象的关系
2、访问对象的构造函数:在原型对象里面有一个constructor属性,该属性指向了构造函数
function Person() {}
// 通过原型对象访问构造函数
console.log(Person.prototype.constructor === Person); // 输出结果:true
// 通过实例对象访问构造函数
var p1 = new Person();
console.log(p1.constructor === Person); // 输出结果:true
案例:用赋值方式修改原型对象为新的对象,就无法访问构造器函数
function Person() {}
// 修改原型对象为一个新的对象
Person.prototype = {sayHello: function() {console.log('hello');}};
var p1 = new Person();
// 使用实例对象p1可以访问新的原型对象中的属性
p1.sayHello(); // 输出结果:hello
// 使用constructor属性无法访问原来的构造函数
console.log(p1.constructor); // 输出结果:Object() { [native code] }
构造函数、原型对象和实例对象之间的关系
3、原型对象的原型对象
原型对象的原型对象:原型对象也是对象,那么这个对象应该也会有一个原型对象存在
function Person() {}
// 查看原型对象的原型对象
console.log(Person.prototype.__proto__);
// 查看原型对象的原型对象的构造函数
console.log(Person.prototype.__proto__.constructor);
4、原型链
①原型链结构特点
- 每个构造函数都有一个prototype属性指向原型对象
- 原型对象通过constructor属性指向构造函数
- 通过实例对象的__proto__属性可以访问原型对象
- Object的原型对象的__proto__属性为null
②原型链结构图
③函数在原型链中的结构
5、成员查找机制 - JavaScript首先会判断实例对象有没有这个成员
- 如果没有找到,就继续查找原型对象的原型对象
- 如果直到最后都没有找到,则返回undefined
function Person() { this.name = '张三';}
Person.prototype.name = '李四';
var p = new Person();
console.log(p.name); // 输出结果:张三
delete p.name; // 删除对象p的name属性
console.log(p.name); // 输出结果:李四
delete Person.prototype.name; // 删除原型对象的name属性
console.log(p.name); // 输出结果:undefined
6、案例:利用原型对象扩展数组方法
Array.prototype.sum = function() {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];}
return sum;
};
var arr = [1, 2, 3];
console.log(arr.sum()); // 输出结果:6
二、this关键字
1、分析this指向
- 构造函数中的this指的是新创建的对象
- 普通函数中的this指向的是window对象
- 将函数作为对象的函数调用时this指向的是该对象
案例演示:this指向
function foo() {
return this;
}
var o = {name: 'Jim', func: foo};
console.log(foo() === window); // 对应第2种情况,输出结果:true
console.log(o.func() === o); // 对应第3种情况,输出结果:true
2、更改this指向:apply()方法和call()方法
- apply(obj,argArray):
- call(obj,arg1,arg2,…):
apply()方法和call()方法的区别
function method(a, b) {
console.log(a + b);
}
method.apply({}, ['1', '2']); // 数组方式传参,输出结果:12
method.call({}, '3', '4'); // 参数方式传参,输出结果:34
3、bind()方法:实现提前绑定的效果。在绑定时,还可以提前传入调用函数时的参数
- bind(obj,arg1,arg2,…)
三、错误处理
1、如何进行错误处理
try{
可能会出现错误的代码
}catch(e){ //代码的错误类型
错误出现后的处理代码
}
案例演示
var o = {};
try {// 在try中编写可能出现错误的代码
o.func();
console.log('a');// 如果前面的代码出错,这行代码不会执行
} catch(e) {// 在catch中捕获错误,e表示错误对象
console.log(e);
}
console.log('b'); // 如果错误已经被处理,这行代码会执行
2、错误对象的传递
function foo1() {
foo2();
console.log('foo1');
}
function foo2() {
var o = {};
o.func(); // 发生错误
}
try {
foo1();
} catch(e) {
console.log('test');
}
3、抛出错误对象
try {
var e1 = new Error('错误信息'); // 创建错误对象
throw e1; // 抛出错误对象,也可以与上一行合并为:throw new Error('错误信息');
} catch (e) {
console.log(e.message); // 输出结果:错误信息
console.log(e1 === e); // 判断e1和e是否为同一个对象,输出结果:true
}
4、错误类型
四、继承
1、借用构造函数继承父类属性
call()方法 :将父类的this指向子类的this,这样就可以实现子类继承父类的属性。
案例
function Father(uname, age) {// Father构造函数是父类
this.uname = uname;
this.age = age;
}
function Son(uname, age, score) {// Son构造函数是子类
Father.call(this, uname, age); // 子类继承父类的属性
this.score = score; // 子类可以拥有自己的特有属性
}
var son = new Son('张三', 18, 100);
console.log(son); // 输出结果:Son {uname: "张三", age: 18, score: 100}
2、利用原型对象继承父类方法
原型对象继承父类方法:将父类的实例对象作为子类的原型对象来使用
function Father() {}
Father.prototype.money = function() {console.log(100000);};
function Son() {}
Son.prototype = new Father(); // 将父类的实例对象作为子类的原型对象
Son.prototype.constructor = Son; // 将原型对象的constructor属性指向子类
new Son().money(); // 调用父类money()方法,输出结果:100000
Son.prototype.exam = function() {}; // 为子类增加exam()方法
console.log(Father.prototype.exam); // 子类不影响父类,输出结果:undefined
原型链示意图
3、class语法的本质:类和构造函数的使用非常相似,可以互相替代
class Person{}
console.log(Person.prototype); // 类也有原型对象
Person.prototype.money = function() {// 类也可以增加方法
console.log(100000);
};
new Person().money(); // 输出结果:100000