原型对象补充
- 构造函数有一个相关联的原型对象,这个原型对象默认是一个空对象{}
- 构造函数的原型对象本身是 Object 类型的 Person.prototype 是 Object,它的构造函数是 Object
- 使用构造函数创建出来的对象会拥有一个构造器属性(constructor),该属性指向创建当前对象的构造函数
- p1.constructor == Person
- 对象本身并没有构造器属性,这个属性是从原型继承来的
- 构造函数的原型对象(空对象 | Object 类型的对象) 它的构造函数是 Object
- 那么他的构造器属性难道不应该是Object吗?
- 构造函数的原型对象神上其实有两个 constructor 属性(实例对象 + 构造函数原型对象的原型对象上面的属性)
继承的实现
- 继承的几种实现方案:
- 属性拷贝(浅拷贝)
- 原型式继承(A.B.C)
- 原型链继承
- 借用构造函数
- 组合继承
- 深拷贝 + 原型式继承
- 属性拷贝和直接赋值的区别
- 如果是直接赋值,那么所有的属性和方法都会被共享
- 如果是属性拷贝,那么只有引用类型的属性会被共享
- 属性拷贝的时候通常只会拷贝实例化属性 | 方法
- 默认情况下,使用 for..in 循环拷贝的时候,会把原型属性和原型方法一并拷贝进去
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(o.hi);
var obj = {
name:"杂货",
friends:['二货','硬货','水货'];
}
var obj2 = obj;
obj.name = '散货';
console.log(obj2.name);
obj.friends.push('傻货');
console.log(obj2.friends);
obj.friends = ['123','456','789'];
console.log(obj2.friends,'++++++');
var o1 = {
arr:['123','234'];
}
var o2 = o1;
console.log(o1.arr == o2.arr);
o1.arr.push('demo');
console.log(o2.arr);
o1.arr= ['demo1','demo2'];
console.log(o2.arr,'+++');
var demo01 = {
show:function(){
console.log('show');
}
}
var demo0 = {};
for(var k in demo01){
demo02[k] = demo01[k];
}
console.log(demo01.show == demo02.show);
demo01.show = function(){
console.log('啦啦啦');
}
demo02.show(); //show
继承的实现(原型式继承)
- 原型式继承
- 利用对象的动态特性 对象 - 原型
- 字面量直接替换 对象 - 原型
- 设置子构造函数的原型对象 = 父构造函数的原型对象
子构造函数 - 父构造函数
缺点是可以获得父构造函数原型对象上面的属性和方法但是无法获得父构造函数的实例对象属性和方法
function Person(name){
this.name = name;
}
Person.prototype.des = '描述信息';
var p1 = new Person('张三');
function Person(name){
this.name = name;
}
Person.prototype = {
constructor:Person,
des:"默认的描述信息",
var p1 = new Person('张三');
}
function Person(name){
this.name = '张三';
}
Person.prototype = {
constructor:Person,
des:'默认的描述信息',
showDes:function(){
console.log(this.des);
}
}
//设置构造函数的原型对象为父构造函数的原型对象
Boy.prototype = Person.prototype;
var boy1 = new Boy();
console.log(boy1.des);
boy1.showDes();
boy1.lol();
扩展内置对象
- 扩展内置对象(把需要共享的属性和方法写在内置构造函数的原型对象上)
- 原型对象上面所有的属性和方法都会被该构造函数创建出来的对象共享,共享可能会导致一些问题
- 如果所有的开发人员都往内置构造函数的原型对象上面添加属性和方法,那么原型对象上的属性和方法会越来越多,难以管理,难以维护,还可能会出现覆盖的问题
把需要共享的属性和方法写在内置构造函数的原型对象上
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 = ['demo01','demo02'];
arr2.name = 'arr2的名称';
arr2.desLog = function(){
console.log('arr2的des');
}
console.log(arr2);
需求:要求所有的数组都拥有 name 和 desLog 方法
Array.prototype.name = 'name';
var arr1 = [1,2,3,4,5];
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]);
}
安全的扩展内置对象
- 提供一个自定义的构造函数(MyArray)
- 设置该构造函数的缘溪行对象为内置构造函数的一个实例
- 在构造函数的原型对象上面添加属性和方法
- 使用自定义的构造函数来创建对象并且使用
//提供一个自定义的构造函数 (MyArray)
function MyArray(){}
//设置该构造函数的原型对象为内置构造函数的一个实例
MyArray.prototype = new Array(); //获得 Array 的所有属性和方法
// MyArray.prototype = Array.prototype;
//设置原型对象
MyArray.prototy[e.name = '默认的名称';
MyArray.prototype.desLog = function(){
console.log('哈哈哈哈');
}
//使用自定义的构造函数来创建对象并且使用
var myArr01 = new MyArray();
myArr01.push('123','345');
console.log(myArr01);
console.log(myArr01.name);
myArr01.desLog();
var myArr02 = new MyArray();
//其他开发人员
function OtherArray(){};
OtherArray.prototype = new Array();
//OtherArray.prototype = Array.prototype;
OtherArray.prototype.name = '默认的名称+++++++';
OtherArray.prototype.desLog = function(){
console.log('嘿嘿嘿嘿');
}
var other01 = new OtherArray();
other01.push('demo1','demo2');
console.log(other01);
console.log(other01.name);
var arr = new MyArray();
console.log(arr.name);
原型链的结构
- 每个对象都是由构造器函数创建出来的
- 每个构造函数都有一个与之相关联的原型对象(prototype)
- 构造函数的原型对象本身也是对象,因此构造函数的原型对象有自己的构造函数
- 构造函数的原型对象的构造函数也有相关联的原型对象,而这个原型对象也是一个对象,因此也有构造函数
- 构造函数的原型对象的构造函数的原型对象也有构造函数,也有原型对象。。也是对象。。构造函数
以上会形成一种链式的访问你结构,这种结构称为原型链,原型链的重点是 Object.prototype,Object.prototype 的原型对象是 null (Object.prototype.proto == null),所有对象原型链的重点都是 Object.prototype
function Person(){}
var p1 = new Person();
// p1 是一个对象,因此有构造函数(person)
//构造函数都有原型对象因此 Person.prototype 存在(Object)
//Person.prototype 本身是一个对象(Object),因此也有构造函数(Object)
//Object 的构造函数也有原型对象, Object.prototype
//Object.prototype 本身也是一个对象,这个对象是 Object 类型
//Object.prototype 也有构造函数,构造函数是 Object
原型链中属性的搜索规则
- 对象在访问(读取|写)属性得时候,先遍历当前的对象查找有没有指定的属性
- 如果有该属性,那么就直接使用
- 如果没有该属性,那么就遍历当前对象的原型对象,在原型对象身上查询指定的属性
如果找到那么就直接使用
如果没有找到那么就继续向上查找 - 重复这个过程直到 Object.prototype, 如果找到那么就使用,如果没有找到那么就返回 Undefined 或者报错
function Person(){
this.name = 'Person-name';
this.age = 20;
}
Person.prototype.showName = function(){
console.log(this.name);
}
function Boy(){
//this.name = 'Boy-name';
}
Boy.prototype = new Person();
Boy.prototype.name = '哈哈';
var boy1 = new Boy();
console.log(boy1.name); // Boy-name
console.log(boye1.age); //undefined
boy1.showName(); //Boy-name
var p1 = new Person();
console.log(p1.name);
原型链的继承
- 子构造函数.prototype = new 父构造函数()
function A(){
this.a = 'a';
}
A.prototype.showA = function(){
console.log('a');
}
function B(){
this.b = 'b';
}
//要求 B 的实例对象拥有 A 构造函数原型对象上面的成员 + 实例对象上面的成员
B.prototype = new A();
var b1 = new B();
console.log(b1.a);
b1.showA();
//b1.constructor
复杂的原型链示例
- 建议
- 属性写在实例对象上面,把方法写在原型对象上面
- 动物(颜色 - 跑)-人(名字-吃饭)-学生(班级-看书)-男生(GF-打游戏)
- 先写构造函数
- 设置原型链继承
- 在构造函数内部设置实例属性,并且在原型对象上面添加方法
- 修正构造器属性
- 创建对象
- 原型链继承的注意点
- 要注意设置原型链继承的位置:先完成原型链继承,再给原型对象添加成员
- 完成原型链继承之后最好修正构造器属性
- 完成原型链继承之后,不能使用字面量的方式来替换原型对象(继承会失效)
function Animal(){
this.color = '红色';
}
Animal.prototype.run = function(){
console.log('run');
}
function Person(){
this.name = '人';
}
Person.prototype = new Animal();
Person.prototype.constructor = Person;
Person.prototype.eat = function(){
console.log('吃饭');
}
function Stydent(){
this.className = '超神班';
}
Student.prototype = new Person();
//Student.prototype.constructot = Student;
//Student.prototype/read = function(){console.log('阅读');}
//以下是错误演示
//Student.prototype = {
// constructor:Student,
// read:function(){
// "读书"
// }
//}
function Boy(){
this.girlF = '林志玲';
}
Boy.prototype = new Student();
Boy.prototype.constructor = Boy;
Boy.prototype.play = function(){
console.log('打游戏');
}
var boy = new Boy();
console.log(boy);
boy.eat();
原型链继承的问题
- 原型链继承的问题
- 父构造函数实例成员(属性+方法)会称为子构造函数创建对象的原型成员,有共享问题
- 无法对父构造函数传递参数
function Person(name){
this.name = name;
this.friends = ['巴拉巴拉','哗啦哗啦','嘀嗒嘀嗒'];
}
Person.prototype.showName = function(){
console.log(this.name);
}
function Boy(){}
Boy.prototype = new Person('张三');//设置实现原型链继承
var boy1 = new Boy();
console.log(boye1.friends);
var boy2 = new Boy();
console.log(boye2.friends);
boy1.friends.push('呜啦呜啦');
console.log(boy.friends);
console.log(boy2.friends);
console.log(boy1.name);
console.log(boy2.name);
Object.create()方法
- Object.create()方法作用
- 创建对象并且设置圆形对象
- 用法:var o = Object.create(obj);
- 创建一个空的对象o 并且设置这个对象的原型对象是 obj
- 注意点:兼容性处理(ES5)
var obj = {
name:"张三",
showNmae:function(){
console.log(this.name);
}
}
var o = Object.create(obj);
//var o = {}
// o.__proto__ = obj;
console.log(o);
console.log(o.name);
o.showName
兼容处理
先判断是否支持 Object.create,如果不支持那么我们就利用对象的动态特性来自己提供一个方法
var obj = {
name:"张三",
showName:funciton(){
console.log(this.name);
}
};
var o ;
if(typeof Object.create == 'function'){
o = Object.creat(obj);
}else{
//o = {};
//o.__proto__ = obj; //非标准的属性
// Object.getPrototypeOf(o) = obj;
Object.create = function(){
function F(){};
F.prototype = obj;
o = new F();
}
}
提供一个函数来封装这个功能,在函数内部先判断是否支持 Object.create,如果支持那么就使用 Object.create,否则我们就自己实现
var obj = {
name:"张三",
showName:function(){
console.log(this.name);
}
}
function creat(objT){
if(typeof Object.create == 'function'){
return Object.create(objT);
}else{
funtion F(){};
F.prototype = objT;
return new F();
}
}
//调用函数
var o = creat(obj);
思考
- 内置构造函数也是对象
- 内置构造函数也是由构造函数创建的。 Object这个对象(构造函数)也有构造函数
Object.assign()方法
- 作用 用来拷贝属性的,一次性拷贝多个对象的属性
- 用法: Object.assign(目标对象,要拷贝属性的对象1,要拷贝属性的对象2,要拷贝属性的对象3。。。)
注意点: - 新特性有兼容性问题
- 原型成员是否能够拷贝?答案是不能(默认不会拷贝原型成员)
使用 for .. in 遍历拷贝对象的时候,会联通该对象的原型成员一起拷贝
<script>
var o = {
name:"张三"
}
var o2 = {
age:20
}
var obj = {};
for(var i in o)
{
obj[i] = o[i];
}
</script>
<script>
var o = {};
Object.assign(o, {name:"张三"},{age:20},{showName:function () {
console.log("name");
}})
console.log(o);
o.showName();
function F() {}
F.prototype.hi = "hi";
var f = new F();
var obj = {};
for (var i in f)
{
if(f.hasOwnProperty(i))
{
obj[i] = f[i];
}
}
console.log(obj.hi);
var demo = {};
Object.assign(demo,f);
console.log(demo.hi);
</script>
call 和 apply 函数
- 来源
所有的对象方法都拥有这两个函数(方法),这两个方法写在 Function.prototype 上面 - 作用
借用其他对象的方法 - 用法:
对象1.方法.call(借用者对象,参数1,参数2,参数3...)
//方法内部的 this 会绑定给 call 的第一个参数
对象1.方法.appply(借用者对象,[参数1,参数2,参数3...]) - 区别(传递参数):
- 参数传递不一样
- 期望的形参长度不一样
- 函数.length 形参的个数
call:length 1期望一个参数
apply:length 2 期望两个参数
var p1 = {
name:"张三",
showName:function () {
console.log(this.name);
},
showDes:function (str1,str2) {
console.log("des" + str1,str2);
}
}
var p2 = {
name:"李四"
}
p1.showName(); //张三
p1.showName.call(p2); //p2借用p1对象的showName方法
p1.showName.apply(); //window.name = 空("")
//注意call和apply传递参数的区别
p1.showDes.call(p2, "啦啦啦啦,暗黑魔法咒语","伏地魔");
p1.showDes.apply(p2,["啦啦啦啦,暗黑魔法咒语","哈利波特"]);
function demo(str1,str2,str3) {
}
console.log(demo.length);
console.log(p1.showDes.call.length);
console.log(p1.showDes.apply.length);
借用构造函数继承的基本写法
- 获取父构造函数实例对象的成员(解决传递参数的问题)
- 问题:还需要获取父构造函数的原型成员
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.showName = function () {
console.log(this.name);
}
function Boy(name,age) {
//构造函数内部会默认创建空对象并赋值给this
Person.call(this,name,age); //this.name = name; 借用构造函数
}
var boy1 = new Boy("张三",20);
var boy2 = new Boy("李四",30);
console.log(boy1);
console.log(boy2);
boy1.showName();
组合继承写法
- 组合继承
- 借用构造函数 + 原型式继承
- 新的问题:原型对象爱过你的共享问题
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.showName = function () {
console.log(this.name);
}
function Boy(name,age) {
//构造函数内部会默认创建空对象并赋值给this
Person.call(this,name,age); //this.name = name; 借用构造函数
}
//原型链继承
//Boy.prototype = new Person(); //原型链继承
Boy.prototype = Person.prototype; //原型式继承C
var boy1 = new Boy("张三",20);
var boy2 = new Boy("李四",30);
console.log(boy1);
console.log(boy2);
boy1.showName();
深拷贝和浅拷贝
- 浅拷贝:for...in 循环直接拷贝(引用类型的属性共享)
- 深拷贝:
使用:for ..in 来遍历
乳沟是值类型,直接赋值
如果是引用类型,新创建一个对象,再次遍历。。持续这个过程
var person = {
name:"张三",
car:{
type:"飞船",
color:"黑色",
des:{
number:666888
}
},
arr:[1,2,3,4]
}
// var p = {};
// for(var i in person)
// {
// p[i] = person[i];
// console.log("____"); //0x11
// }
//参数1:目标对象 ,参数2:要拷贝属性的对象
function deepCopy(obj1,obj2) {
obj1 = obj1 || {}; //容错性处理
for(var i in obj2)
{
//只拷贝实例属性
if (obj2.hasOwnProperty(i))
{
if (typeof obj2[i] == "object")
{
//引用类型,新创建对象,再次遍历
//需要判断是数组还是对象
obj1[i] = Array.isArray(obj2[i])?[]:{};
deepCopy(obj1[i],obj2[i]);
}else
{
obj1[i] = obj2[i];
}
}
}
}
var p = {};
deepCopy(p, person);
console.log(p);
//验证共享的问题是否解决
person.car.type = "银河战舰";
console.log(p.car.type);
console.log(person);
通过深拷贝实现继承
function deepCopy(obj1,obj2) {
obj1 = obj1 || {}; //容错性处理
for(var i in obj2)
{
//只拷贝实例属性
if (obj2.hasOwnProperty(i))
{
if (typeof obj2[i] == "object")
{
//引用类型,新创建对象,再次遍历
//需要判断是数组还是对象
obj1[i] = Array.isArray(obj2[i])?[]:{};
deepCopy(obj1[i],obj2[i]);
}else
{
obj1[i] = obj2[i];
}
}
}
}
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.showName = function () {
console.log(this.name);
}
Person.prototype.hi = "hi";
function Boy(name,age) {
//构造函数内部会默认创建空对象并赋值给this
Person.call(this,name,age); //this.name = name; 借用构造函数
}
deepCopy(Boy.prototype,Person.prototype);
var boy1 = new Boy("张三",20);
var boy2 = new Boy("李四",30);
console.log(boy1);
console.log(boy2);
boy1.showName();
console.log(boy1.hi);
Boy.prototype.hi = "hahahahah";
console.log(boy1.hi);
var p1 = new Person();
console.log(p1.hi);