JavaScript 中继承的概念
通过某种方式,让一个对象可以访问到另一个对象中的属性和方法,我们把这种方式称之为继承(并不是所谓的 xxx extends yyy)。
为什么要使用继承
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</style>
</style>
</head>
<body>
<script type="text/javascript">
function Student(name, grade) {
this.name = name;
this.grade = grade;
this.say = function() {
};
}
var student1 = new Student('小白', '初二');
var student2 = new Student('小黑', '初二');
console.log(student1.say === student2.say);
</script>
</body>
</html>
由上述例子可见,对象 student1 和对象 student2 都有 say 方法,这两个 say 方法功能相似,但不是同一个方法(console.log(student1.say === student2.say) 的结果为false),因此会造成内存浪费(没有指向同一块内存)。
解决方案:把 say 方法写在他们共同的父对象(Student.prototype)中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</style>
</style>
</head>
<body>
<script type="text/javascript">
function Student(name, grade) {
this.name = name;
this.grade = grade;
}
Student.prototype.say = function() {
}
var student1 = new Student('小白', '初二');
var student2 = new Student('小黑', '初二');
console.log(student1.say === student2.say);
</script>
</body>
</html>
结论:只要往某个构造函数的 prototype 对象中添加某个属性、方法,那么这样的属性、方法都可以被通过构造函数创建的实例所共享。
继承的实现方式
- 原型链继承
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</style>
</style>
</head>
<body>
<script type="text/javascript">
function Student() {
}
var student1 = new Student();
Student.prototype.say = function(str) {
console.log(str);
}
</script>
</body>
</html>
上述代码中,为了让 student1 有一个 say 的方法,将 say 方法放在了 Student.prototype (student1 的原型对象)中。添加1、2个方法无所谓,但是如果方法很多会导致代码冗余。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</style>
</style>
</head>
<body>
<script type="text/javascript">
function Student() {
}
var student1 = new Student();
Student.prototype.say1 = function() {}
Student.prototype.say2 = function() {}
Student.prototype.say3 = function() {}
Student.prototype.say4 = function() {}
</script>
</body>
</html>
上述代码中,给 student 的原型对象添加了好多方法,代码产生了不少冗余。为了减少这些冗余,可以这样写:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</style>
</style>
</head>
<body>
<script type="text/javascript">
function Student() {
}
var student1 = new Student();
Student.prototype = {
say1:function() {},
say2:function() {},
say3:function() {},
say4:function() {}
}
// undefined
console.log(student1.say1);
</script>
</body>
</html>
上述代码中,虽然减少了冗余,但是 console.log(student1.say1) 的属性为 undefined。原因是:student1 对象在创建的时候已经有了一个确定的原型对象(旧原型对象),而 Student.prototype 后面被重新赋值(新原型对象),但是 student1 对象的原型对象没有改变,所以 student1 对象并不能访问到新原型对象中的方法(say1、say2、say3、say4)。
解决办法:先改变原型,再创建对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</style>
</style>
</head>
<body>
<script type="text/javascript">
function Student() {
}
// 改变原型
Student.prototype = {
say1:function() {},
say2:function() {},
say3:function() {},
say4:function() {}
}
// 创建对象
var student1 = new Student();
console.log(student1.say1);
</script>
</body>
</html>
注意:一般情况下,对于新原型对象,会添加一个 constructor 属性(属性值为对象名),从而不破坏原有的原型对象的结构
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</style>
</style>
</head>
<body>
<script type="text/javascript">
function Student() {
}
// 改变原型
Student.prototype = {
constructor: Student,
say1:function() {},
say2:function() {},
say3:function() {},
say4:function() {}
}
// 创建对象
var student1 = new Student();
console.log(student1.say1);
</script>
</body>
</html>
- 拷贝继承
场景:有时候想使用某个对象中的属性,但是又不能直接去修改它,于是就可以创建一个该对象的拷贝,然后再修改该属性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</style>
</style>
</head>
<body>
<script type="text/javascript">
var source = {
name: "小白",
age: 15
};
// target 对象和 source 对象指向的是同一个内存地址,若改变 target 对象中属性的值,source 对象的属性值也会改变
// var target = source;
// console.log(target === source);
// 创建一个该对象的拷贝
var target = {};
for (var key in source) {
target[key] = source[key];
}
// 修改拷貝的对象
target.name = "小黑";
</script>
</body>
</html>
浅拷贝与深拷贝:
浅拷贝只是拷贝一层属性。若当对象中的属性内部也是一个对象时,根据自己需要,可以使用深拷贝。深拷贝其实是利用递归的原理,将对象的若干层属性都拷贝出来。
- 三点运算符(es6)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</style>
</style>
</head>
<body>
<script type="text/javascript">
var source = {
name: "小白",
age: 15
};
var target1 = {...source};
// {name: "小白", age: 15}
console.log(target1);
var target2 = {...source, name: "小黑"};
// {name: "小黑", age: 15}
console.log(target2);
var target3 = {gender: "女", ...source, name: "小红"};
// {gender: "女", name: "小红", age: 15}
console.log(target3);
</script>
</body>
</html>
- assign() 函数(es6)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
var source = {name:'Tom', age: 18};
var target1 = Object.assign({}, source);
// {name: "Tom", age: 18}
console.log(target1);
var target2 = Object.assign({name:'Petter', gender: '男'}, source);
// {name: "Tom", gender: "男", age: 18}
console.log(target2);
var target3 = Object.assign(source, {name:'Petter', gender: '男'});
// {name: "Petter", age: 18, gender: "男"}
console.log(target3);
</script>
</body>
</html>
- 原型式继承
场景:
1)创建一个纯洁的对象:对象中没有任何属性(constructor、hasOwnProperty、toStirng...)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</style>
</style>
</head>
<body>
<script type="text/javascript">
var target = Object.create(null);
console.log(target);
</script>
</body>
</html>
2)创建一个继承自某个父对象的子对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</style>
</style>
</head>
<body>
<script type="text/javascript">
var source = {
name: "小白",
age: 15,
say: function() {
console.log("My name is"+ this.name);
}
};
var target = Object.create(source);
target.name = "小黑";
target.say();
</script>
</body>
</html>
- 借用构造函数实现继承
原理:函数的 call、apply 调用方式
局限性:父类构造函数代码必须完全适用于子类。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</style>
</style>
</head>
<body>
<script type="text/javascript">
function Student(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
function Teacher(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
</script>
</body>
</html>
在上述代码中,Student 对象和 Teacher 对象都有 name 属性、age 属性、gender 属性,代码显得冗余,可以这样优化:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</style>
</style>
</head>
<body>
<script type="text/javascript">
function Student(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
function Teacher(name, age, gender) {
// 这种函数的调用方式,函数内部的 this 指向的是 window,相当于给 window 对象的 name属性、age属性、gender属性赋了值
// Student(name, age, gender);
// 先将 Student 函数内部的 this 指向 Teacher 对象的实例
// 然后再给 Teacher 对象的实例的name属性、age属性、gender属性赋值
// Student.call(this, name, age, gender) 等价于 Student.apply(this, [name, age, gender])
Student.call(this, name, age, gender);
this.salary = 6000;
}
var teacher1 = new Teacher("大白", 30, '男');
</script>
</body>
</html>
网友评论