面向对象之原型对象的继承day03
JavaScript面相对象的继承
一.in操作符与hasOwnProperty的用法和区别
in操作符:
①遍历对象
②判断(检查)对象中是否存在指定的属性(实例成员 + 原型成员)
语法:'属性' in 对象
hasOwnProperty:
检查对象是否拥有这个属性(只检查自己的成员,实例成员)
语法:对象.hasOwnProperty(属性|方法);
<script>
//1.创建一个空对象
var o = {};
//2.提供一个已经有的对象obj
var obj = {
name:'对象的属性',
age:20,
demo:{nme:"lalala"},
sayHello:function (){
console.log('hello');
}
};
//3.混入式继承(把原有对象的所有属性和方法都拷贝给新的对象)
for (var k in obj){
if(obj.hasOwnProperty(k)) {
o[k] = obj[k];
}
}
//注意:利用for...in赋值和直接赋值不一样,for...in赋值只有引用类型的属共享,其他的值类型不共享,
//直接赋值的话,直接赋值:完全共享.赋值的是地址指针,所得属性都是共享的.
//4,打印查看o对象
console.log(o.des);
console.log(o);//object
console.log(obj.des);//undefined
obj.sayHello = function (){
console.log('改变了');
};
//5.问题:新对象的引用类型属性和旧对象中的对应属性是否相等
console.log(o.sayHello == obj.sayHello,"___");//true
console.log(o.sayHello == obj.sayHello);//false
//注意:在修改方法的时候,这里的方法是使用函数声明了.重新设置了一个声明的函数,重新定义了一个函数的地址空间,所以这里的函数方法不是共存的所以方法不相等.
console.log(o.sayHello);
console.log(obj.sayHello);
//问题:那么两个对象中的其中某个修改了该属性,则另一个对象会受到影响
console.log(o.demo == obj.demo);
o.demo.nme = "nnnn";
console.log(o.demo == obj.demo);
</script>
补充说明:
eg:<script>
function Person(){
this.name = "默认"
}
Person.prototype.hi = "hi";
var p1 = new Person();
var o = {};
/*for (var k in p1)
{
if(p1.hasOwnProperty(k))
{
o[k] = p1[k];
}
}
console.log(o.hi);*/
</script>
<script>
var obj = {
name:"123",
showName:function(){
console.log("12345");
}
}
/* var obj2 = obj;*/
var obj2 = {};
//直接赋值:完全共享
/* obj.name = "sss";
console.log(obj2.name);*/
for(var k in obj){
if(obj.hasOwnProperty(k)){
obj2[k] = obj[k];
}
}
console.log(obj.showName == obj2.showName);//true
obj.showName = function (){
console.log('改变了');
}
console.log(obj.showName == obj2.showName);
</script>
图形解析:
(1)
![](https://img.haomeiwen.com/i4350809/870c1c4cdb7c0f4a.png)
(2)
![](http://upload-images.jianshu.io/upload_images/4350809-eade5960d2e68989.png)
(3)
![](http://upload-images.jianshu.io/upload_images/4350809-53c75bf6fffb3943.png)
二.原型对象的补充说明
1.构造函数有一个相关联的原型对象,这个原型对象默认是一个空对象{}(实际上这个原型对象拥有一个属性为constructor属性的对象).
2.构造函数的原型对象本身是一个Object类型的,Person.prototype (原型对象)是Object,他的构造函数是Object
3.使用构造函数创建出来的对象会拥有一个构造器属性(constructor),该属性指向创建当前对象的构造函数.
4.对象本身没有构造器属性,这个属性是从原型对象上继承来的.
eg:
<script>
function Person(){
}
var p1 = Person();
// console.log(p1.constructor == Person);//报错
console.log(Person.prototype.constructor == Person);//true
</script>
console.log(Person.prototype);//Object
var o = {};
console.log(o);
console.log(Person.prototype.constructor);//Person这个对象的(访问的是自己的实例属性)
delete Person.prototype.constructor;
console.log(Person.prototype.constructor);//Object这个对象的(访问的是原型对象上面的属性)
问题???
构造函数的原型对象(空对象|Object类型),他的构造函数是Object,那么他的构造器属性难道不应该是Object吗?
解答:
构造器的原型对象身上其实有两个(constructor)属性(实例属性 + 构造函数的原型对象的原型对象上面的属性)
图形解析:
![](http://upload-images.jianshu.io/upload_images/4350809-4353fa69f5db1655.png)
三.继承的几种实现方案
- 属性拷贝(浅拷贝)
- 原型式继承(A.B.C)
- 原型链继承
- 借用构造函数
- 组合继承
- (深拷贝 + 原型式)组合继承
知识点1:属性拷贝和直接赋值的区别:
01 属性拷贝:
引用类型: (数组):引用会共享,如果重新设置,那么会切断联系
引用类型:(函数):函数会共享,如果重新设置,那么会切断联系.
==注意==
属性拷贝的包括成员和原型对象
02 直接赋值:
直接赋值:完全共享!
数组: 数组会共享,如果重新设置,那么另一个对象会受影响.
函数: 函数会共享,如果重新设置,那么另一个对象会受影响.
==1.属性拷贝继承:==
eg1:
<script>
var obj1 = {
arr :[1,2,3],
show:function (){
console.log('show');
}
}
var o = {};
//属性拷贝
for(var i in obj1){
o[i] = obj1[i];
}
console.log(o);
console.log(o.arr);
console.log(obj1.arr);
//重新设置值
obj1.arr = ['demo1','demo2'];
console.log(o.arr);
console.log(obj1.arr);//受影响
</script>
eg2:
<script>
function Person(){
this.name = '默认的名称';
this.friends = ['123','456','789'];
}
Person.prototype.hi = 'hi';
var p1 = new Person();
var o = {};
//属性拷贝
for(var k in p1){
if(p1.hasOwnProperty(k)){//排除了拷贝原型对象
o[k] = p1[k];
}
}
console.log(o);
console.log(p1);
o.name = '我是一个数字';
console.log(p1.name);//默认名称
p1.friends.push('我给改变了');
console.log(o.friends);
console.log(p1.hi);
console.log(o.hi);//undefined
</script>
图像解析1:
![](http://upload-images.jianshu.io/upload_images/4350809-ec4552e02cbb67c3.png)
图形解析2:
![](http://upload-images.jianshu.io/upload_images/4350809-766babeb51d205ac.jpg)
==直接赋值拷贝==
eg1:
<script>
var demo1 = {
arr:[1,2,3],
show:function (){
console.log('show');
}
};
//直接赋值
var demo2 = demo1;
console.log(demo1.arr);
console.log(demo2.arr);
demo1.arr = ['test1','test2'];
console.log(demo1.arr);
console.log(demo2.arr);
demo1.show();
demo2.show();
demo1.show = function (){
console.log('hahaha');
}
demo1.show();
demo2.show();
</script>
图形解析:
![](http://upload-images.jianshu.io/upload_images/4350809-8f8783a956af90e4.png)
==2.原型式继承==
A 利用对象的动态特性 (对象-原型)
B 字面量直接替换 (对象-原型)
C 设置子构造函数的原型对象 = 父构造函数的原型对象 (子构造函数-父构造函数)
问题:可以获得父构造函数原型对象上面的属性和方法,但是无法获得父构造函数的实例对象上的属性和方法.
eg:
A 利用对象的动态特性 对象-原型
<script>
function Person (name){
this.name = name;
}
Person.prototype.des = '描述信息';
var p1 = new Person('张三');
</script>
B 字面量直接替换 对象-原型
<script>
function Person(name){
this.name = name;
}
Person.prototype = {
constructor:Person,
des:'默认的描述信息'
}
var p1 = new Person('张三');
</script>
C 设置子构造函数的原型对象=父构造函数的原型对象 子构造函数-父构造函数
<script>
function Person(){
this.name = '张三';
}
Person.prototype = {
constructor:Person,
des:'默认的描述信息',
showDes:function (){
console.log(this.des);
}
}
function Boy() {
this.lol = function (){
console.log('lol');
}
}
//设置子构造函数的原型对象为父构造函数的原型对象,便可以继承父构造函数的原型对象的属性和方法
Boy.prototype = Person.prototype;
var boy1 = new Boy();
console.log(boy1.des);//默认描述信息
boy1.showDes();
boy1.lol();
</script>
图形解析:
![](http://upload-images.jianshu.io/upload_images/4350809-801be897692c4a04.jpg)
==3.扩展内置对象==
定义:
把需要共享的属性和方法写在内置构造函数的原型对象上.
问题:
01 原型对象上面所有的属性和方法都会被该构造函数创建出来的对象共享,共享可能会导致一些问题.
02 如果所有的开发人员都往内置构造函数的原型对象上面添加属性和方法,那么原型上面的属性和方法会越来越多,难以管理,难以维护,还可能会出现覆盖问题.
eg:
<script>
//需求:要求给arr1添加属性name|desLog
var arr1 = [1,2,3];
console.log(arr1);//arr1的构造函数为Array
console.log(arr1.constructor);
arr1.name = 'arr1的名称';
arr1.desLog = function (){
console.log('arr1的des');
};
console.log(arr1);
//需求:要求给arr2添加属性name|desLog
var arr2 = ['demo1','demo2'];
arr2.name = 'arr2的名称';
arr2.desLog = function (){
console.log('arr2的des');
}
console.log(arr2);
//需求:要求所有的数组都拥有name和desLog方法?
</script>
<script>
Array.prototype.name = '默认的名称';
Array.prototype.desLog = function (){
console.log('默认的描述信息');
}
var arr1 = [1];
var arr2 = ['demo1'];
console.log(arr1.name);
console.log(arr2.name);
arr1.desLog();
arr2.desLog();
</script>
注意:共享可能会导致一些问题如下
<script>
Array.prototype.name = 'name';
var arr1 = [1,2,3,4];
for (var i = 0;i<arr1.length;i++){
console.log(i,arr1[i]);
}
console.log('---------------');
for(var i in arr1){
console.log(i,arr1[i]);//会打印出name name
}
//两次遍历打印的结果不一样
</script>
安全的扩展内置对象:
01 提供一个自定义的构造函数(空函数)(MyArray)
02 设置该构造函数的原型对象为内置构造函数的实例
03 在构造函数的原型对象上面添加属性和方法
04 使用自定义的构造函数来创建对象并且使用
eg:
<script>
//1.提供一个自定义的构造函数
function MyArray(){}
//2.设置该构造函数的原型对象为内置构造函数的一个实例
MyArray.prototype = new Array();//获得Array的所有的属性和方法
//MyArray.prototype = Array.prototype;
//3.设置原型对象
MyArray.prototype.name = '默认名称';
MyArray.prototype.desLog = function (){
console.log('哈哈');
}
//4.使用自定义的构造函数来创建对象并且使用
var myArr01 = new MyArray();
myArr01.push('123','345');
console.log(myArr01);
console.log(myArr01.name);//默认名称
myArr01.desLog();//哈哈
var myArr02 = new MyArray();
console.log(myArr02);
//其他的开发人员
function OtherArray (){};
OtherArray.prototype = new Array();
OtherArray.prototype.name = '默认名称++++++++++++';
OtherArray.prototype.desLog = function (){
console.log('嘿嘿');
}
var other1 = new OtherArray();
other1.push('demo1','demo2');
console.log(other1);
console.log(other1.name);
var arr = new MyArray();
console.log(arr.name);//默认名称
</script>
图形解析:
![](http://upload-images.jianshu.io/upload_images/4350809-530966c243f3a588.png)
原型链的结构
- 其实在js中所有的对象都是由构造函数创建出来的
- 所有的构造函数的原型对象所有一个相关联的原型对象
- 所有构造函数的原型对象本身也是一个对象,所以构造函数的原型对象也是由构造函数创建出来的
- 构造函数的原型对象的构造函数也有一个与之相关联的原型对象,这个原型对象本身也是一个对象
- 构造函数的原型对象的原型对象也有自己的构造函数
- 构造函数的原型对象的原型对象也有自己的原型对象.以上会形成一种链式结构,这个结构就是原型链.
- 在原型链的顶端(终点)Object.prototype
- Object.prototype本身也是一个对象,所以也有自己的构造函数===>Object
- Object.prototype的原型对象应该是它的构造函数的原型对象 Object.prototype (推理 不成立)
Object.prototype.__proto__=Object .prototype.constructor.prototype[Object.prototype]
- js处理:Object.prototype.__proto__ = null;
图形解析:
image
原型链中属性的搜索规则:
-
对象在访问属性的属性的时候,首先先查找实例对象的成员(属性|方法)
-
如果找到了,那么就直接使用,如果没有找到,那么就去它的原型对象上面查找,如果找到了就直接使用.
-
如果原型上面也没有找到,那么就继续搜索原型对象的原型对象,如果找到了就继续使用,如果没有找到,那么就继续上面的过程.
-
...重复上面的搜索过程
-
如果查找到Object.prototype也没有找到,那么句undefined或者是报错.
eg: <script> function Person(){ this.name = '默认' } Person.prototype.name = '原型上面的名称'; Person.prototype.age = '原型上面的年龄'; function Boy(){ this.lol = 'lol'; this.name = '男孩'; } Boy.prototype = new Person(); var boy = new Boy(); console.log(boy.name);//男孩 console.log(boy.age);//原型上面的年龄 console.log(boy.des);//undefined </script>
==4.原型链继承==
形式: 子构造函数.prototype = new 父构造函数();
步骤:
-
提供构造函数
-
设置属性和方法
-
设置继承(原型链继承)
-
修正构造器属性
eg: <script> function A(){ this.a = 'a'; } A.prototype.desA = 'desA'; function B(){ this.b = 'b'; } //设置原型链继承 B.prototype = new A(); var b = new B(); console.log(b.a); console.log(b.desA); </script>
使用注意点:
- 在设置完原型链接继承之后,要修正构造函数的原型对象的构造器属性
- 设置原型对象的属性和方法这个操作需要注意位置,建议在完成原型链继承之后再添加成员(属性和方法)
- 在设置完原型链继承之后,不能使用字面量的方式来替换当前构造函数的原型对象(如果替换,继承结构会丢失)
不足:
01 无法向父构造函数传递参数
02 父构造函数的实例属性变成了子构造函数创建出来的对象的原型属性
eg:
<script>
function Person(name){
this.name = name;
this.friends = ['小花','哗啦','巴拉']
}
Person.prototype.hi = 'hi';
function Boy(){}
//设置原型链继承
Boy.prototype = new Person('张三');
Boy.prototype.constructor = Boy;
var boy = new Boy();
console.log(boy);
var boy2 = new Boy();
console.log(boy2);
console.log(boy.friends);//?
console.log(boy2.friends);//?
boy.friends.push('taotao');
console.log(boy.friends);
console.log(boy2.friends);
</script>
==5.Object.create的方法说明:==
-
Object是内置构造函数
-
Object.Create()静态方法(直接添加在构造函数上面的方法)
-
作用:创建对象,并且指向原型对象
-
注意点:该方法是ES5推出,所以有兼容性问题
eg: <script> var obj = { name:'张三' } /* var o = {}; o.__proto__ = obj; console.log(o.name);*/ var o = Object.create(obj);//创建一个对象,并且指定这个对象的原型对象为obj console.log(o); </script>
兼容性处理:
1.先判断是否支持create方法,如果不支持,那么我们就是要临时的构造函数来设置原型对象并创建对象的实例
2.先判断是否支持,如果不支持,那么我们就为Object主动地提供create方法
3.抽取一个函数,把设置原型的过程封装起来
eg:
<script>
var demo = {
name:'demo'
}
if(typeof Object.create == 'function'){
var o = Object.create(demo);
}else{
//var o = {};
//o.__proto__ = demo; //不推荐这样写 换一种写法
//设置原型对象
//访问原型对象的几种方法
//01 构造函数.prototype
//02 对象.__proto__
//03 Object.getPrototypeOf
function F(){}
F.prototype = demo;
var o = new F();
}
</script>
<script>
var demo = {
name:'demo'
}
if(typeof Object.create == 'function'){
var o = Object.create(demo);
}else{
Object.create = function (){
function F(){}
F.prototype = demo;
var o = new F();
}
}
</script>
<script>
function Create(obj){
if(typeof Object.create == 'function'){
var o = Object.create(obj);
}else{
function F(){}
F.prototype = obj;
return new F();
}
}
var obj = {age:23};
var o = Create(obj);
</script>
==6.Object.assign()方法==
作用: 拷贝多个对象的属性(不会拷贝对象的原型成员)而利用for...in...拷贝会拷贝原型成员
语法:Object.assign(目标对象,要拷贝的对象...);
eg:
<script>
var obj1 = {
name:'默认的名称'
}
var obj2 = {
age:23
}
var obj3 = {
book:'小说'
}
var obj = {};
Object.assign(obj,obj1,obj2,obj3);
console.log(obj);
</script>
<script>
function Person (){
this.name = '张三'
}
Person.prototype.hi = 'hi';
var p1 = new Person();
var o = {};
Object.assign(o,p1);
console.log(o);
console.log(o.hi);//undefined
</script>
==7.call和apply的用法说明==
call和apply是js中所有对象都拥有的两种方法
作用:借用对象的方法
call和apply的作用一模一样
区别:传递参数不一样
call:
第一个参数:谁调用(借用)就传递谁,传递的是谁,那么函数内部的this就指向谁
其他参数:参数列表的方式传递
对象.方法.call(this绑定的对象,参数1,参数2,参数3,...)
apply:
第一个参数:谁调用(借用)就传递谁,传递的是谁,那么函数内部的this就指向谁
其他参数:数组元素
对象.方法.apply(this绑定的对象,[参数1,参数2,参数3,...])
eg:
<script>
var obj1 = {
name:'张三',
showName:function (param1,param2){
console.log(this.name + param1 + param2);
}
}
var obj2 = {
name:'李四'
}
/*obj1.showName();
obj2.showName();*///obj2要使用obj1的showName方法
obj1.showName.call(obj2,'demo1','demo2');
//obj1.showName.apply(obj2);
obj1.showName.apply(obj2,['demo1','demo2']);
</script>
==8.this.的指向说明详解:==
this 指向的永远都是一个对象(在非严格模式下)
函数的调用
-
01 以普通函数方式调用 this -->window
-
02 以对象的方法来调用 this --->对象
-
03 new 构造函数的方式来调用 this--->指向内部新创建的对象
-
04 函数上下文的方式来调用(call|apply) this--->绑定的第一个参数 如果不传递参数那么默认指向window
eg: <script> function func(){ console.log(this); } //1.以普通函数调用 func();//window var obj = {name:'test'}; obj.funcTest = func; //2.以对象的方法来调用 obj.funcTest();//obj var o = { test:function (){ console.log(this); } } o.test.call();//window </script>
==9.借用构造函数继承的基本写法.==
借用构造函数继承的基本写法:
通过借用父构造函数获得父构造函数的实例属性
思考:无法获得父构造函数的原型成员
eg:
<script>
function Person(name){
this.name = name;
}
Person.prototype.hi = 'hi';
function Boy(name,lol){
//在构造函数内部默认会创建空对象并且赋值给this
this.lol = lol;
//获取Person构造函数的实例属性
Person.call(this,name);
}
var boy = new Boy('张三','英雄联盟');
console.log(boy.name);
var boy2 = new Boy('李四','CF');
console.log(boy2.name);
console.log(boy.lol);
console.log(boy2.lol);
console.log(boy.hi);//undefined
console.log(boy2.hi);//undefined
</script>
组合构造函数继承的基本写法:
eg:
<script>
function Person(name){
this.name = name;
}
Person.prototype.hi = 'hi';
function Boy(name,lol){
//在构造函数内部默认会创建空对象并且赋值给this
this.lol = lol;
//获取Person构造函数的实例属性
Person.call(this,name);
}
//设置原型链继承
Boy.prototype = Person.prototype;//原型式继承C(继承原型对象)
var boy = new Boy('张三','英雄联盟');
console.log(boy.name);
var boy2 = new Boy('李四','CF');
console.log(boy2.name);
console.log(boy.lol);
console.log(boy2.lol);
console.log(boy.hi);//hi
console.log(boy2.hi);//hi
console.log(boy);
</script>
==10.深拷贝和浅拷贝:==
浅拷贝:for...in obj[k] = obj1[k] 引用类型会分享
深拷贝:对浅拷贝的方式进行改进
eg:
<script>
var o = {
name:'张三',
friends:['demo1','demo2'],
car:{
type:'奔驰'
}
}
function deepCopy(obj1,obj2){
obj1 = obj1 || {};
for(var k in obj2){
if(obj2.hasOwnProperty(k)){
//判断是值类型还是引用类型
if(typeof obj2[k] == 'object'){
//如果是引用类型,那么就再次循环
//注意:obj1[k]要做初始化处理 ES5
obj1[k] = Array.isArray(obj2[k]?[]:{}); //检查是什么类型的?如果是数组[],如果是对象{}
deepCopy(obj1[k],obj2[k]);
}else{
obj1[k] = obj2[k];//值类型
}
}
}
}
var demo = {};
deepCopy(demo,o);
console.log(demo);
demo.car.type = '奥迪';
console.log(o.car.type);
console.log(o);
console.log(demo);
</script>
==11.深拷贝与借用构造函数实现继承==
01 通过借用父构造函数获得父构造函数的 实例属性
02 思考:无法获得父构造函数的原型成员
eg:
<script>
function deepCopy(obj1,obj2) {
obj1 = obj1 || {};
for(var k in obj2)
{
if (obj2.hasOwnProperty(k)){
//判断是值类型还是引用类型
if (typeof obj2[k] == "object")
{
//如果是引用类型,那么就再次循环
//注意:obj1[k]要做初始化处理 ES5
obj1[k] = Array.isArray(obj2[k])?[]:{}; //检查是什么类型的?如果是数组[],如果是对象{}
deepCopy(obj1[k],obj2[k]);
}else
{
obj1[k] = obj2[k]; //值类型
}
}
}
}
function Person(name){
this.name = name;
}
Person.prototype.hi = 'hi';
Person.prototype.car = {
type:'飞船'
}
function Boy(name,lol){
//在构造函数内部默认会创建空对象并且赋值给this
this.lol = lol;
//获取Person构造函数中的实例属性
Person.call(this,name);
}
//设置原型链继承
//Boy.prototype = new Person();
//Boy.prototype = Person.prototype; //原型式继承C
deepCopy(Boy.prototype,Person.prototype);
var p1 = new Person();
var boy = new Boy('李四','CF');
console.log(p1.car);//飞船
console.log(boy.car);//飞船
p1.car.type = '傻逼了';
console.log(p1.car);//傻逼了
console.log(boy.car);//傻逼了
</script>