这是我参与8月更文挑战的第20天,活动详情查看:8月更文挑战
前言
昨天我们主要把继承的知识先告了一段落,今天我们主要搞搞练习,直接开搞。
一、原型链继承-练习
这版块的练习其实也比较简单,之前的文章都有提到。简单说明一下。
我们这里书写3个构造函数:Animal(动物类),Person(人),Student(学生类)
其中:
- Animal的实例属性上有name,age;原型方法上有eat,run
- Person的实例属性上有job,原型方法上有jump
- Student的实例属性上有className,原型方法上有study
也有同学觉得文字好像不太懂啥意思,图我也准备好了
动手前咋们先捋一捋,第一点构造函数长啥样?什么是实例属性?什么是原型方法? 不管前2个可能需要想想,但是原型我们应该立马脑子要浮现一个叫prototype的东西。对吧,这样一来代码就好写了。
那就开始写代码了喔,注释都写在代码里了哈。
// ------------- Animal ------------ //
function Animal(name, age) {
// 实例属性
this.name = name;
this.age = age;
}
// 原型方法
Animal.prototype.eat = function () {
console.log('吃');
};
Animal.prototype.run = function () {
console.log('跑');
}
// ------------- Person ------------ //
function Person(job) {
// 实例属性
this.job = job;
}
// 原型方法
Person.prototype.jump = function () {
console.log('跳');
};
// ------------- Student ------------ //
function Student(className) {
// 实例属性
this.className = className;
}
// 原型方法
Student.prototype.study = function () {
console.log('学习');
}
继承继承,没继承怎么完整呢?接下来我们就来实现继承
<!--Animal-->
<script>
function Animal(name, age) {
this.name = name;
this.age = age;
}
Animal.prototype.eat = function () {
console.log(this.name + '会吃');
};
Animal.prototype.run = function () {
console.log(this.name + '会跑');
}
</script>
<!--Person-->
<script>
function Person(job, name, age) {
// 1. 借助构造函数继承 改变this
Animal.call(this, name, age);
this.job = job;
}
// 2. 原型链继承 和 寄生式继承
function Temp() {}
Temp.prototype = Animal.prototype;
// Temp实例
var personPrototype = new Temp();
// 修改constructor指向
personPrototype.constructor = Person;
Person.prototype = personPrototype;
// 注意注意注意.... 这里是Person的原型方法
Person.prototype.jump = function () {
console.log(this.name + '会跳');
};
</script>
<script>
var p = new Person('厨师', '小明', 18);
console.log(p);
p.run();
p.eat();
p.jump();
</script>
<!--Student-->
<script>
function Student(className) {
this.className = className;
}
Student.prototype.study = function () {
console.log('学习');
}
</script>
我们来看看结果是怎么样的
这里我们可以看到 __proto__指向了Animal,即使是Person实例,也可以调用Animal的run方法和eat方法。
二、继承-应用练习(扩展内置对象)
方式1
直接给对象动态添加属性和方法
var arr = [1, 2, 3];
arr.run = function () {
console.log('跑');
};
arr.run();
var arr2 = [2];
arr2.run();
弊端:
- 如果操作很多个对象, 则无法共享
- 代码比较冗余
方式2
直接给Array原型对象添加方法
Array.prototype.push = function () {
console.log('跑');
};
var arr = [1, 2, 3];
arr.push('111'); // 跑
console.log(arr); // [1, 2, 3]
var arr2 = [2];
arr2.push('2222'); // 跑
console.log(arr2); // [2]
弊端: 可能会产生覆盖的情况
为什么push之后输出跑而不是4呢?arr输出不是 [1, 2, 3, "111"]。这就是直接给Array原型对象添加的弊端了呀,就产生了一个覆盖的操作。
方式3
提供一个新的构造函数
修改构造函数的原型指向为数组的原型对象,为了能够获取数组里面的属性和方法。
问题: 依然会修改数组原型对象内容
优化:
原型对象就是一个对象
可以直接根据Array创建一个对象, 给新创建的函数原型对象进行赋值
function MyArray() {}
MyArray.prototype = new Array();
MyArray.prototype.run = function () {
console.log('跑');
};
var arr2 = new MyArray();
console.log(arr2); // MyArray()
arr2.run(); // 跑
三、知识点补充
1. 回顾
- 函数的prototype属性
- 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
- 原型对象中有一个属性constructor, 它指向函数对象
- 原型对象添加属性(一般都是方法)
- 函数的所有实例对象自动拥有原型中的属性(方法)
2. call和apply
两个方法是Function的原型对象上面的方法
两个函数的作用是一样的,都可以用来借用方法实现
参数:
-
第一个参数是调用该方法的对象(函数内部的this绑定的对象)
-
第二个参数
- call:参数列表
- apply:数组 实例:
-
对象1.方法.call(调用方法的真正的对象,参数1,参数2,参数3);
-
对象1.方法.apply(调用方法的真正的对象,[参数1,参数2,参数3...])
3.this总结
this -- > 总是指向一个对象
函数的调用方式
-
001 作为对象的方法来调用 this--->当前的对象
-
002 作为普通的函数调用 this--->window
-
003 作为构造函数和new使用 this--->构造函数内部新创建的对象
-
004 被call或者是apply调用(函数上下文调用) this--->第一个参数
四、继承-拷贝属性
1. 手动拷贝
遍历一个对象的键值对,分别动态的添加到另外一个对象上
var obj = {
name: 'Jack',
age: 19
};
var copyObj = {};
for (var key in obj) {
copyObj[key] = obj[key];
}
obj.name = 'Tom';
console.log(obj); // {name: "Tom", age: 19}
console.log(copyObj); // {name: "Tom", age: 19}
2. Object.assign
语法: Object.assign(参数1, 参数2, 参数3..)
第一个参数是目标对象,后面参数是要拷贝属性的对象
var obj = {
name: 'Jack',
age: 19
};
var copyObj = {};
Object.assign(copyObj, obj, {
address: '北京'
});
obj.name = 'Tom';
console.log(obj); // {name: "Tom", age: 19}
console.log(copyObj); // {name: "Jack", age: 19, address: "北京"}
五、继承-拷贝属性-深拷贝
弊端
如果属性的值是引用类型的,那么子对象和父对象共享一块数据,修改了某个对象对另外一个对象有影响
解决方案
深拷贝/浅拷贝的区别在于对引用值的处理
-
浅拷贝, 直接拷贝一份地址
-
深拷贝-拷贝地址对应的具体内容
深拷贝实现思路
-
01 提供一个函数,两个参数(元对象,要拷贝属性的对象)
-
02 在函数中先检查第一个参数是否有值,如果没有值那么就初始化一个空的对象
-
03 for..in循环来变量参数2
-
001 检查当前的属性值是什么类型
-
002 如果是值类型,那么就直接拷贝赋值
-
003 如果是引用类型,那么就再调用一次这个方法,去内部拷贝这个对象的所有属性
-
代码如下:
// 1. 要拷贝的对象
var obj = {
name: 'Jack',
age: 18,
friends: ['小明', '小花'],
goodFri: {
name: 'Rose',
age: 16,
adress: '北京',
hobby: [{
name: '羽毛球'
}, {
name: '乒乓球'
}]
},
};
function deepCopyObj2NewObj(fromObj, toObj) {
// 在函数中先检查第一个参数是否有值,
// 如果没有值那么就初始化一个空的对象
for (var key in fromObj) {
var fromValue = fromObj[key];
if (!isObj(fromValue)) {
toObj[key] = fromValue;
} else {
// 如果是引用类型,那么就再调用一次这个方法,去内部拷贝这个对象的所有属性
var temObj = new fromValue.constructor;
deepCopyObj2NewObj(fromValue, temObj);
toObj[key] = temObj
}
}
}
function isObj(obj) {
return obj instanceof Object;
}
function isArr(obj) {
return Array.isArray(obj);
// return Object.prototype.toString.call(obj) === '[object Array]'
}
var newObj = {};
deepCopyObj2NewObj(obj, newObj);
obj.goodFri.hobby[0]['category'] = 'ball'; // 不会被拷贝
console.log('obj ==> ', obj);
console.log('newObj ==> ', newObj);
看看控制台输出结果吧
到这里练习也搞了,面向对象的继承就算是结束啦!不知道你们有没有收获呢?