前面一直用存货,现在把最后的东西都放出去了,又该开始学习新内容了
23深拷贝和浅拷贝
内存
内存分为四个区域:栈 堆 全局静态区 只读区
假定只有局部变量,只看堆和栈,
1)、 定义局部变量 age,由于age是局部变量,所以在栈中申请内存空间,起名为age,又由于给age赋的值250是基本类型,所以,值直接存储在栈中。
2)、定义局部变量arr,由于arr是局部变量,所以在栈中申请空间,但是arr的内存中存储的是什么?由于给arr赋的值不是基本类型,而是引用类型(new出来的),所以,先在堆中申请空间存放数据 12,23,34,。再把堆区的地址赋给arr。
赋值
如果给arr[0]赋值的话,arr1[0]的值也会发生变化,因为,arr和arr1保存着相同的地址,它门两个引用的数据是共享的。就像你在很多地方(简历的那张纸,户口本上的那张纸)会写上你的家庭地址。这么多张纸都引用着你家。根据一张纸上找到你家,给你家放上一百万的现金(数据改变了,相当于arr[0]=10),再根据另外一张纸的地址也找到了你家,你发现你一百万在(不要给我说被人拿了)
如果在上面的基础上增加一句代码:arr[0]=10;那么内存将会有如下变化:
深拷贝和浅拷贝:
其实在第二点已经说到了拷贝,所谓拷贝,就是赋值。把一个变量赋给另外一个变量,就是把变量的内容进行拷贝。把一个对象的值赋给另外一个对象,就是把一个对象拷贝一份。
1.基本类没有问题,
因为,基本类型赋值时,赋的是数据(所以,不存在深拷贝和浅拷贝的问题)。
如:
Var x = 100;
Var y = x; //此时x和y都是100;
如果要改变y的值,x的值不会改变。
2.引用类型有问题
因为,引用类型赋值时,赋的值地址(就是引用类型变量在内存中保存的内容)
var arr1 = new Array(12,23,34)
Var arr2 = arr1;//这就是一个最简单的浅拷贝
如果要改变arr2所引用的数据:arr2[0]=100时,那么arr1[0]的值也是100。
原因就是 arr1和arr2引用了同一块内存区域(以上的第二点中有体现)。
这是最简单的浅拷贝,因为,只是把arr1的地址拷贝的一份给了arr2,并没有把arr1的数据拷贝一份。所以,拷贝的深度不够
3.用json对象的方式(也是引用类型)来演示浅拷贝和深拷贝
var p = {
"id":"007",
"name":"刘德华",
"books":new Array("三国演义","红楼梦","水浒传")//这是引用类型
}
深拷贝
var p2 = {};
for(let key in p){
if(typeof p[key]=='object'){
p2[key]=[];//因为,我上面写的是数组,所以,暂时赋值一个空数组.
for(let i in p[key]){
p2[key][i] = p[key][i]
}
}else{
p2[key] = p[key];
}
}
p2.books[0] ="四国";
console.log(p2);
console.log(p);
递归实现深拷贝
var p = {
"id":"007",
"name":"刘德华",
"wife":{
"id":"008",
"name":"刘德的妻子",
"address":{
"city":"北京",
"area":"海淀区"
}
}
}
//写函数
function copyObj(obj){
let newObj={};
for(let key in obj){
if(typeof obj[key] =='object'){//如:key是wife,引用类型,那就递归
newObj[key] = copyObj(obj[key])
}else{//基本类型,直接赋值
newObj[key] = obj[key];
}
}
return newObj;
}
let pNew = copyObj(p);
pNew.wife.name="张三疯";
pNew.wife.address.city = "香港";
console.log(pNew);
console.log(p);
深拷贝_如果属性是数组等非键值对的对象
就得单独处理:要么给数组增加一个自我复制的函数(建议这样做),要么单独判断。
//给数组对象增加一个方法,用来复制自己
Array.prototype.copyself = function(){
let arr = new Array();
for(let i in this){
arr[i] = this[i]
}
return arr;
}
var p = {
"id":"007",
"name":"刘德华",
"books":new Array("三国演义","红楼梦","水浒传")//这是引用类型
}
function copyObj(obj){
let newObj={};
for(let key in obj){
if(typeof obj[key] =='object'){//如:key是wife,引用类型,那就递归
newObj[key] = obj[key].copyself();
}else{//基本类型,直接赋值
newObj[key] = obj[key];
}
}
return newObj;
}
var pNew = copyObj(p);
pNew.books[0] = "四国";
console.log(pNew);
console.log(p);
24面向对象的特点
封装
指利用抽象数据类型将数据和基于数据的操作封装在一起,成为一个独立实体,数据被保护在内部,尽可能隐藏内部细节,只保留一些对外接口与外部发生联系。
好处:封装之后成为独立实体,独立实体可以在不同环境复用,提高复用性,只提供了安全的操作接口,实体更加安全
继承
子类继承父类,使得子类拥有父类的属性,或者子类从父类继承方法。
好处:在不同的类可能有共同的特征和动作,可以把共同部分放到一个类中,让其它类共享。将一个通用类拓展为多个特定类,继承可以再共同部分的基础上拓展功能,避免重复,更易于维护和拓展,可以不修改其他代码,继承出新的特定类并实现新的方法。
多态
前提是已经有封装成的独立体,独立体之间存在继承关系。
多态是同一个行为具有多个不同表现形式或形态的能力。是同一个行为发生在不同对象会产生不同的效果。
多态存在的三个必要条件:继承、方法覆盖、父类型引用指向子类型对象。
public class Animal {
public void move(){
System.out.println("Animal move!");
}
}
public class Cat extends Animal{
//方法覆盖
public void move(){
System.out.println("I can jump");
}
//子类特有
public void catchMouse(){
System.out.println("I can catch mouse");
}
}
[点击并拖拽以移动]
•
public class Bird extends Animal{
//方法覆盖
public void move(){
System.out.println("I can fly");
}
//子类特有
public void catchBug(){
System.out.println("I can catch bugs");
}
}
25webpack
26继承的实现
原型链继承
首先new一个main()的实例,既然是实例那么--proto--就会指向main的原型对象,也就可以访问main挂载到main.prototype上的方法和属性,所以我们把personal的原型对象指向这个main实例对象,此时,personnal的实例对象的constructor被覆盖,所以手动把personnal原型对象的constuctor指向personal,重新建立联系。之后在personal的原型对象上添加属性和方法,此时person的实例对象p就可以既访问..又可以访问...
// 先继承
Personal.prototype = new Main();
Personal.prototype.constructor = Personal;
// 后定义属性和方法
Personal.prototype.name = 'hwk';
Personal.prototype.sayName = function () {
console.log('Personal name')
}
// 正确输出
console.log(p.sex ) ; // 男
console.log(p.name) ; // hwk
p.eat(); // Main eat ...
p.sayName (); // Personal name
![](https://i-blog.csdnimg.cn/blog_migrate/d2fcbb2d544185336365e7d7d985f2ba.png)
构造函数继承
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
//继承了SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
这里subtype构造函数的内部 调用了 supertype的call方法,意思是把父类中通过this指定的属性和方法复制到子类创建的实例中,,然后new的subtype就携带着父类中的属性和方法
组合继承
function SuperType(name){
this.name=name;
this.colors=["red","blue","green"];
}
SuperType.prototype.sayName=function(){
console.log(this.name);
}
function SubType(name,age){
SuperType.call(this,name);
this.age=age;
}
SubType.prototype=new SuperType();
SubType.prototype.constructor=SubType;
SubType.prototype.sayAge=function(){
console.log(this.age);
}
var instance1=new SubType("zxf",24);
instance1.colors.push("black");
console.log(instance1.colors);//["red","blue","green","black"]
instance1.sayName();//"zxf"
instance1.sayAge();//24
var instance2=new SubType("jay",36);
console.log(instance2.colors);//["red","blue","green"]
instance2.sayName();//"jay"
instance2.sayAge();//36
subtype通过构造函数继承的方式,也就是call,把父类的name属性和color属性拿了过来,
之后挂载到supertype上的sayname方法,通过subtype的原型对象,指向一个super实例,然后把构造器重新赋值一下,获得了sayname的方法,然后所有属性和方法就都被继承过来了!