第6章:面向对象的程序设计笔记

本文深入讲解JavaScript中的对象概念,探讨对象的创建方式、属性类型及其特性,并详细介绍多种面向对象编程中的继承模式,如原型链继承、借用构造函数、组合继承等。
  • 面向对象语言标志之一 —有类的概念,通过类可以创建多个具有相同的属性和方法的对象;ECMA中没有类的概念。
  • ECMA-262对象的定义:‘无序属性的集合,其属性可以包含基本值、对象或者函数’;每个对象都是基于一个引用类型创建的,包括原生类型和自定义类型。

6.1 理解对象

用new操作符,创建一个Object的实例;

    var person = new Object();
    person.name = 'JS';
    person.age = '22';
    person.sayName = function () { alert(this.name) };

用对象字面量模式;

	var person = {
		name : 'JS';
	    age : '22';
	    sayName : function () { alert(this.name) }
	}
    

注:属性的创建,都会带有一些特征值,JS通过这些特征值来定义它们的行为;

6.1.1 属性类型 (数据属性 & 访问器属性)

ECMA-262第5版在定义只有内部才用的特性(attribute)时,描述了属性(property)的各种特征。这些特性为了实现JS引擎用的,因为是内布值,所以不可直接访问,并且[[***]]表示
ECMAScript中的两种属性:数据属性和访问器属性。

1. 数据属性
数据属性包含一个数据值的位置,在这个位置读写值;它有4个描述其行为的特性;
[[Configurable]]: 是否可删除(delete)属性且重新定义属性,是否可修改属性的特性,或者是否能把属性修改为访问器属性。
[[Enumerable]]: 是否可用for-in循环返回属性。
[[Writable]]: 是否可修改属性的值。
[[Value]]: 包含这个属性的数据值。从这个位置读取属性值,写入时,把新值保存在这个位置。 默认值为 undefined
注:默认值??

  • 像上面的例子,直接在对象上定义属性时,[[Value]]设置为指定值,其他三个属性都为true。
  • 用Object.defineProperty()方法创建的新属性,不指定,则除了[[Value]]其它三个特性都为false;如果是修改已定义的属性则不受此此限制。

修改属性默认的特性: Object.defineProperty() 【 IE8实现不彻底,IE9+支持】
必须使用ECMAScript5的Object.defineProperty()方法。
参数3个:属性所在对象、属性名字、一个描述符对象;
注:描述符对象的属性必须是:configurable、enumerable、writable、value

	var person = {};
	Object.defineProperty(person, 'name', {
		configurable: false,
		value: 'JS'
	});
	alert(person.name); //  'JS'
	delete person.name;
	alert(person.name); //  'JS'

注:

  1. configurable为 false,使用delete在严格模式下会报错;
  2. 一旦把属性定义为不可配置(就有限制了),就不能再把它变回可配置;

2. 访问器属性
访问器属性不包含数据值;它包含getter和setter函数(两个函数不是必需的)。4个特性:
[[Configurable]]: 是否可删除(delete)属性且重新定义属性,是否可修改属性的特性,或者是否能把属性修改为访问器属性。
[[Enumerable]]: 是否可用for-in循环返回属性。
[[Get]]: 读取属性时调用。默认值undefined。
[[Set]]: 写入时调用。默认值undefined。

通过Object.defineProperty() 定义访问器属性

	let book = {
		_year: 2004,
		edition: 1
	};
	Object.defineProperty(book, 'year', {
		get: function () {
			return this._year
		},
		set: function (neaVal) {
			if (newVal > 2004) {
				this._year = newVal;
				this.edition += newVal - 2004;
			}
		}
	});
	book.year = 2005;
	alert(book.edition); // 2

注:

  • 可以不同时指定getter和setter,但是只指定getter,则只能读不能写;相反,只指定setter,则只能写不能读;
  • 在不支持Object.defineProperty() 的浏览器中不能修改[[Configurable]]和[[Enumerable]]。

6.1.2 定义多个属性:Object.defineProperties()【IE9+支持】

ECMAScript5定义了Object.defineProperties()。利用这个方法通过描述符一次定义多个属性。
参数2个对象:

  • 第一个需要添加或者修改属性的对象;
  • 第二个对象的属性与第一个对象中需要添加或者修改的属性一一对应;
    例如:
	let book = {};
	Object.defineProperties(book, {
		_year: {
			writable: true,
			value: 2004
		},
		edition: {
			writable: true,
			value: 1
		},
		year: {
			get: function () {
				return this._year;
			},
			set: function (newVal) {
				if (newVal > 2004) {
					this._year = newVal;
					this.edition += newVal - 2004;
				}
			}
		}
	})

book对象定义了两个数据属性和一个访问器属性;
其与上一个例子定义的对象相同,区别,这里的属性都是同一时间创建的;

6.1.3 读取属性的特性: Object.getOwnPropertydDescriptor() [IE9+支持]

ECMAScript5定义了Object.getOwnPropertydDescriptor()。
利用这个方法可以取得给定属性的描述符;
任何对象都可以,包括DOM和BOM对象;

参数2个:

  • 第一个属性所属的对象;
  • 需要读取其描述符的属性名称;

返回值:一个对象;

  • 访问属性:configurable、enumerable、get、set
  • 数据属性:configurable、enumerable、writable、value
	let book = {};
	Object.defineProperties(book, {
		_year: {
			writable: true,
			value: 2004
		},
		edition: {
			writable: true,
			value: 1
		},
		year: {
			get: function () {
				return this._year;
			},
			set: function (newVal) {
				if (newVal > 2004) {
					this._year = newVal;
					this.edition += newVal - 2004;
				}
			}
		}
	});
	
	let descriptor = Object.getOwnPropertyDescriptor(book, '_year');
	alert(descriptor .value); // 2004
	alert(descriptor.configurable); // false
	alert(typeof descriptor.get); // 'undefined'
	
	let descriptor2 = Object.getOwnPropertyDescriptor(book, 'year');
	alert(descriptor2 .value); // undefined
	alert(descriptor2.configurable); // false
	alert(typeof descriptor2.get); // 'function'

6.2 创建对象

面向对象三大特性:

  1. 可封装性;
  2. 继承;
  3. 多态;

一般写框架用面向对象的形式,像vue、react一样;vue、react 构架工具以node为基础的;
例子1:

    let arr = [];
    let arr2 = [];

    console.log(arr.push);//ƒ push() { [native code] }
    console.log(arr2.push);//ƒ push() { [native code] }

    console.log(arr.push === arr2.push);//true

    let n = "10";
    let m = "30";

    console.log(n.slice === m.slice);//true
模式缺陷
工厂模式无法解决对象类型识别的问题
构造函数模式每个方法都会在每个实例上重新创建一遍,即不同实例上的同名函数是不相等的
原型模式由它的共享本性所导致的,单个实例不能拥有自己的属性和方法
组合模式(构造函数模式和原型模式)---------
动态原型模式---------
寄生构造函数模式返回对象与构造函数没有关系,所以不能使用instanceof操作符确实对象类型
稳妥构造函数模式

6.2.1 工厂模式

  • 原理:使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。
  • 解决问题:创建多个相似对象的问题,会产生大量的重复代码;考虑到ECMAScript无法创建类,开发人员就发明了这种函数。
  • 缺陷:1.对象类型识别的问题(即怎样知道对象的类型),都是Object创建出来的; 2. 不同实例上的同名函数是不相等的;

例子1;

    function studentInfo(name,age,sex){
        let obj = new Object();
        obj.name = name;
        obj.age = age;
        obj.sex = sex;
        return obj;
    };
     let user1 = studentInfo('leo',30,'男');
    let user2 = studentInfo('li',44,'女');
    console.log(user1);//{name: "leo", age: 30, sex: "男"}
    console.log(user2);//{name: "li", age: 44, sex: "女"}

例子2;

    // 生成一辆汽车
    function createCar(color,lunzi){
        let obj = new Object();
        obj.color = color;  
        obj.lunzi = lunzi;  
        return obj;
    }

    let car1 = createCar('red',4);
    let car2 = createCar('blue',4);
    let car3 = createCar('yellow',4);

问题: 这两个对象的类型区分不开,都是通过Object来创建的

     console.log(user1.constructor);//ƒ Object() { [native code] }
     console.log(car1.constructor);//ƒ Object() { [native code] }

解决:
用构造函数创建特定类型的对象:比如:像Array、Date、Object…这样的原生构造函数;因此,可以创建自定义对象,从而定义自一类对象类型的方法和属性;

    let arr = new Array();
    let d = new Date();

6.2.2 构造函数模式

  • 解决问题:对象类型识别的问题(即怎样知道对象的类型)。
  • 缺陷:每个方法都会在每个实例上重新创建一遍,即不同实例上的同名函数是不相等的,占内存。
    -返回值数据类型默认情况下都是object类型(局限性)。

1. 作为构造函数,首字母大写;这是一种约定惯例
跟其他函数有所区别;因为构造函数也是函数,只是是用来创建对象的函数而已;

2. 必须用new ( 一元运算符)操作符,来调用构造函数(与其他函数的区别)创建对象

调用构造函数经历的4个步骤:

  • 会在构造函数的内部创建一个新的空对象
  • 构造函数内部的this指向这个空对象(this就代表了这个对象)
  • 执行构造函数内的代码;(给这个新对象添加属性)
  • 返回这个新对象

3. 创建自定义构造函数可以将它的实例标识作为一种特定的类型;
4. 创建自定义构造函数是定义在Global对象上的(即window对象上);
例子1:

 	function Car(color,lunzi){
        this.color = color; 
        this.lunzi = lunzi;
        //return {a:1};
    };
    Car(); //直接调用,函数中this指向window,函数内部就是向window添加属性

例子2:

    let c1 = new Car('red',4);
    let c2 = new Car('red',5);
    let c3 = new Car('red',6);
    let c4 = new Car('red',7);
    let c5 = new Car('red',8);

    console.log(c1);//{'red',4}
    //实例标示作为一种特定的类型;
    let c1 = new Car('red',4);
    console.log(c1.constructor);//ƒ Car(color,lunzi){...}
    let arr = new Array(1,2,3,4);
    console.log(arr1.constructor);//ƒ Array() { [native code] }

    // 以下数组都是通过Array创建的,这些数组都是一类
    let arr1 = [];
    let arr2 = [];
    let arr3 = [];
    let arr4 = [];
    let arr5 = [];

5. 将构造函数当作函数;
构造函数与其他函数的区别:

  • 构造函数与其他函数的唯一区别,在于调用它们的方式不同
  • 任何函数,只要是通过new操作法调用的,就可以作为构造函数;
  • 任何函数,只要不是通过new操作法调用的,就是普通函数;

不用new操作符调用构造函数例如:

例子1:

    function Car(color,lunzi){
        this.color = color; 
        this.lunzi = lunzi;
    };
    Car(‘red’,4); //undefined;直接调用,函数中this指向window,函数内部就是向window添加属性
    console.log(window.Car)//ƒ Car(color,lunzi){this.color = color; this.lunzi = lunzi; } 

例子2:

    function Car(color,lunzi){
        this.color = color; 
        this.lunzi = lunzi;
        return {a:1};///return基本类型不影响构造函数返回值,返回值=还是那么隐士对象;如果是引用类型函数返回值为return后面的引用类型值,创建者也变成引用类型的构造函数;
    };  
	注意:不要在构造函数中使用return;也不要直接调用构造函数;(call、apply)

6. 构造函数的问题;
主要问题:每个方法都会在每个实例上重新创建一遍;即不同实例上的同名函数是不相等的;

    function Car(color,lunzi){
            this.color = color; 
            this.lunzi = lunzi;

        // 目的是:通过Car创建对象,共享city和run
        // 实际上,每次调用Car函数,都会创建一个run属性对应的函数,每一个对象都拥有单独的函数,不是共享的
        this.city = '北京';
        this.run = function (){
            console.log("我会跑了");    
        }
    }

    let c1 = new Car('red',4);
    let c2 = new Car('blue',4);

    console.dir(c1);//Car {color: "red", lunzi: 4, city: "北京", run: ƒ}
    console.log(c2);//Car {color: "blue", lunzi: 4, city: "北京", run: ƒ}
    //说明:每创建一个实例,就会执行一次构造函数内的代码,也就创建了不同的function实例;但是这些function的功能是一样的,没有必要创建多次占内存;     
    console.log( c1.run === c2.run );//false

    let arr1 = [];
    let arr2 = [];//原生构造函数的方法是相等的;
    console.log(arr1.pop === arr2.pop);//true

6.2.3 原型模式

  • 原理:每一个函数都拥有一个属性叫prototype,每一个对象上都有一个
    __ proto __对象属性。prototype是一个指针,指向__proto __对象,即:property就是通过构造函数而创建的那个对象实例的原型对象。
  • 解决问题:让所有对象实例共享它所包含的属性和方法。
  • 缺陷:由它的共享本性所导致的,单个实例不能拥有自己独有的属性和方法。

1. prototype 原型
每一个函数都拥有一个属性叫prototype,就是原型。-+*通过这个函数创建出来的一类对象拥有的共享的属性和方法,都放在这个函数的原型上;原型对应的值是一个对象(默认是通过Object创建出来的,可以被改写)

例子1:

 /*
        把一类对象共享的属性和方法,放在原型上

    */
    function Car(color,lunzi){
        this.color = color; 
        this.lunzi = lunzi;

    }

    // 内部原型对象的值:Car.prototype = {};

    Car.prototype.run = function (){
        console.log('我被开走了');   
    }
    Car.prototype.city = '北京';

    console.dir( Car.prototype );

    let c1 = new Car('red',2);
    let c2 = new Car('blue',2);

    console.log(c1.run);//f(){console.log('我被开走了');}
    console.log(c2.run);//f(){console.log('我被开走了');}
    console.log( c1.run === c2.run );//true


    // 通过Array创建的数组,都拥有原型上的方法
    console.log(Array.prototype);//打印出的是数组所有共享的方法:例如:push、pop、shift...

    // 给数组扩展一个abc方法  避免这样做
    Array.prototype.abc = function (){
        alert("我是扩展的ABC")   
    }

    let arr = [];

    arr.abc(123);//弹出:我是扩展的ABC

2. __ proto __
每一个对象上都有一个__ proto __属性,值指向的是创建这个对象的构造函数的原型

例子1:

    let arr1 = [];

    // 数组上的__proto__指向的是创建数组的Array构造函数的原型
    console.log(arr1.__proto__ === Array.prototype);//true

3.原型链
一个对象查找一个属性的一套规则
当查找一个属性,先从自身查找,自己身上没有,就继续找创建自己的构造函数的原型,直到找到Object.prototype为止,没找到返回undefined

例子1:
Object.prototype.run = ‘456’;
function Car(color,lunzi){
this.color = color;
this.lunzi = lunzi;
}

    // 原型是函数上的属性

    // Car.prototype.run = function (){
    //  console.log('我被开走了');   
    // }
    Car.prototype.city = '北京';

    console.log('原型', Car.prototype );//原型 {city: "北京", run: ƒ, constructor: ƒ}

    let c1 = new Car('red',2);

    //c1.run = 123;
    console.log(c1);//Car{color:'red';lunzi:'4'}
    console.log(c1.run);//456  自己身上没有,找Car的原型上也没有,找Object的原型上有;

例子2:

     Object.prototype.run = '456';
     function Car(color,lunzi){
        this.color = color; 
        this.lunzi = lunzi;
    }

    Car.prototype.city = '北京';

    let c1 = new Car('red',2);


    console.log( c1.__proto__.__proto__.run);//456
    过程:c1.__proto__ = Car.prototype;
        c1.__proto__.__proto__ = Car.prototype.__proto__
        Car.prototype.__proto__ = Object.prototype;
        c1.__proto__.__proto__ = Object.prototype;
        c1.__proto__.__proto__.run = Object.prototype.run;

【原型模式】
【检测对象的数据类型】

  1. typeof 一元运算符

    优点缺点
    判断原始类型比较方便null返回的是object
    方法返回的是function
    所有的引用类型都返回object,Array、Date等不能准确定位
  2. constructor 构造函数
    函数的原型是一个对象,对象初始的时候有一个属性为constructor
    constructor的值指向的是拥有这个原型的函数
    问题:constructor可以被改写,所以会误判;

	function Into(){ };
        let i = new Into;
        let i2 = new Into;
        let arr = new Array();
        Into.prototype.abc = function(){
            a:123
        }
        console.log(arr)
    
    console.log(i.constructor === Into)//true;
    console.log(i2.constructor === Into)//true;
    console.log(i.abc)
    console.log(i2.abc)
    //问题:constructor可以被改写,所以会误判;
    Into.prototype.constructor = [1,2,3]
    console.log(i.constructor === Into)//false;
    console.log(i2.constructor === Into)//false;
  1. instanceof 二元运算符
    作用:运算一下这个函数的原型是不是在这个对象的原型链上;
    语法: 对象 instanceof 函数;
    返回值:返回布尔值;

    优点缺点
    判断对象的具体类型只能判断对象,对原始类型不能判断
    多全局对象时返回不正确(跨窗口或跨frame操作)

问题:instanceof ,找的是原型链上的(Object)也不靠谱

     function fo(){  }
     fo.prototype = {
        abc1(){},
        abc2(){}
    }

    let cat = new fo;

    console.log(cat instanceof fo);//true
    console.log(cat instanceof Array);//false
     //问题:instanceof ,找的是原型链上的(Object)也不靠谱;
    console.log(cat instanceof Object);//true 不靠谱找到最顶层啦!
  1. Object.prototype.toString 对象的toString方法;(几乎完美的方法)
    例子1:

     console.log(Array.prototype)//打印出来里边含有:toString()方法;
     console.log([1,2,3].toString())//"1,2,3"
     console.log([].toString())//没有值;
     //所以需要跳过数组的toString方法,用Object的toString方法;
     console.log({}.toString())//[object Object]
     object:调用toString方法后,返回值是什么类型的;
     Object:是一个深了类型的对象
    

例子2:

    console.log(Object.prototype.toString.call([]))////[object Array]
    console.log(Object.prototype.toString.call([]).slice(8,-1)==="Array")//true;
    console.log(Object.prototype.toString.call(9).slice(8,-1)==="Number")//true;
    console.log(Object.prototype.toString.call("g").slice(8,-1)==="String")//true;
    console.log(Object.prototype.toString.call(new Date).slice(8,-1)==="Date")//true;
    console.log(Object.prototype.toString.call(Math).slice(8,-1)==="Math")//true;

【枚举原型链属性\枚举自身属性\取出属性的描述数据】

  1. for-in语句
    定义:枚举原型链上允许被枚举的属性;
    如果需要只枚举自身的属性,需要用obj.hasOwnProterty(属性名)判断过滤一下;

例子1:

    Object.prototype.kk = "我是object"
    function Into2(){};

    Into2.prototype.abc = function(){
        a:1;
    }
    let t2 = new Into2;
    t2.hh = "10";

    for(var attr in t2){
        console.log(attr)//hh kk abc   由此看出for-in 枚举的是原型链上的可以被枚举的属性;像__proto__、constructor这些属性虽然每个对象都有,但是它们是不可被枚举的;
    }
    for(var attr in t2){
        if(t2.hasOwnProperty(attr)){
            console.log(attr)//hh 如果需要只枚举自身的属性,需要用对象.hasOwnProterty(属性名)判断过滤一下;
        }

    }
  1. obj.hasOwnProperty(属性名)
    定义:判断一个属性是不是这个对象自身的;不会找原型链上的;
    返回值:返回布尔值;

例子1:

    Object.prototype.kk = "我是object"
    function Into2(){ };

    Into2.prototype.abc = function(){
        a:1;
    }
    let t2 = new Into2;
    t2.hh = "10";

    console.log(t2.hasOwnProperty("hh"))//true 只会找自身
    console.log(t2.hasOwnProperty("bb"))//false 只会找自身
    console.log(t2.hasOwnProperty("abc"))//false 不找原型链
    console.log(t2.hasOwnProperty("kk"))//false 不找原型链
  1. Object.getOwnProtopretyDescriptor();
    作用:
    取出来这个属性的描述数据
    返回值:
    返回指定对象上一个自有属性对应的属性描述符。
    (自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
    语法:
    Object.getOwnPropertyDescriptor()

例子1:

 	Object.prototype.kk = "我是object"
    function Into2(){};

    Into2.prototype.abc = function(){
        a:1;
    }
    let t2 = new Into2;
    t2.hh = "10";
    console.log(Object.getOwnPropertyDescriptor(Into2.prototype,"constructor"))//Object { value: Into2(), writable: true, enumerable: false, configurable: true }不可被枚举;
    console.log(Object.getOwnPropertyDescriptor(Into2.prototype,"__proto__"))//undefined 原型链上的属性,此方法属性必须是直接赋予该对象的属性

【综合实例】

  1. 面向对象写拖拽

     ` function Tuozhuai(box){
             console.log(this)//指的是创建出来的实例;
             this.box = box;//传进来的元素挂在实例身上;
         }   
         Tuozhuai.prototype.init = function(){
             this.box.onmousedown = this.downFn.bind(this);//把downFn方法中的this指向改为实例;
         };
         Tuozhuai.prototype.downFn = function (ev){
             //把disX和disY挂在实例身上;
             this.disX = ev.clientX - this.box.offsetLeft;
             this.disY = ev.clientY - this.box.offsetTop;
            document.onmousemove = this.moveFn.bind(this);//把moveFn方法中的this指向改为实例;原本为document;
             document.onmouseup = this.upFn.bind(this);
         };
         Tuozhuai.prototype.moveFn = function (ev){
             console.log(this.box)//指的是挂在实例上的元素;
             console.log(this)//指的是创建的实例;
             this.box.style.left = ev.clientX - this.disX + 'px';
             this.box.style.top = ev.clientY - this.disY + 'px';
         };
         Tuozhuai.prototype.upFn = function () {
             document.onmousemove = document.onmouseup = null;
         };
         let box = document.getElementById('box');   
         let tuo = new Tuozhuai(box);
         console.log(tuo.box)
         tuo.init();`
    
  2. 面向对象写拖拽 (简写)

    class Drag{
     	constructor(box){
         console.dir(this)//Drag{box: div#box2
         //downFn: ƒ (); moveFn:f(); upFn:f()
         this.box = box;//传进来的元素挂在实例身上;
         this.downFn = this.downFn.bind(this);//把downFn方法中的this指向改为实例;
         this.moveFn = this.moveFn.bind(this);//把moveFn方法中的this指向改为实例;原本为document;
         this.upFn = this.upFn.bind(this);
     }
     Init(){
         this.box.onmousedown = this.downFn;
     }
     downFn(ev){
         //把disX和disY挂在实例身上;
         this.disX = ev.clientX - this.box.offsetLeft;
         this.disY = ev.clientY - this.box.offsetTop;
    
         document.onmousemove = this.moveFn;
         document.onmouseup = this.upFn;
     }
     moveFn(ev){
         console.log(this.box)//指的是挂在实例上的元素;
         console.log(this)//指的是创建的实例;
         this.box.style.left = ev.clientX - this.disX + 'px';
         this.box.style.top = ev.clientY - this.disY + 'px';
     }
     upFn() {
         document.onmousemove = document.onmouseup = null;
     }
     };  
     let box2 = document.getElementById('box2'); 
     let tuo2 = new Drag(box2);
     tuo2.Init();
    

6.2.4 组合使用构造函数模式和原型模式

定义引用类型的一种默认模式。
目前ECMAScript使用最广泛、认可度最高的一种创建自定义类型的方法。
构造函数模式定义实例属性,原型模式定义方法和共享的属性。

	function Person (name, age, job) {
		this.name = name;
		this.age = age;
		this.job =  job;
		this.friends = ['Shelby', 'Court'];
	};
	Person.prototype = {
		constructor : Person;
		sayName : function () {
			alert(this.name);
		};
	};
	var person = new Person('Nicholas', '29', 'Software English');
	var person1 = new Person('Greg',  '27', 'Doctor');
	person.friends.push('Van');
	alert(person.friends); // ['Shelby', 'Court', 'Van']
	alert(person1.friends); // ['Shelby', 'Court']
	alert(person.friends === person1.friends); // false
	alert(person.sayName === person1.sayName); // true

6.2.5 动态原型模式

对独立的构造函数和原型,进行封装;全部封装到构造函数中,通过在构造函数中初始化原型(仅在必要的情况下),同事保持使用构造函数和原型的优点。

	function Person (name, age, job) {
		this.name = name;
		this.age = age;
		this.job =  job;
		this.friends = ['Shelby', 'Court'];
		// 只在初次调用,完成初始化
		if (typeof this.sayName !== 'function') {
			Person.prototype.sayName = function () {
				alert(this.name);
			};
		}
	};

6.2.6 寄生构造函数模式

  • 原理:除了使用new操作符并把包装函数叫做构造函数外,其他跟工厂模式一模一样。(重写了构造函数的返回值)
  • 解决问题:不能直接重写原生构造函数的同时,添加一些额外的方法。
  • 缺陷:由于返回对象与构造函数无关,所以不能用instanceof操作符确定对象类型。

寄生构造函数模式与工厂模式的区别:

  • 构造函数默认的返回值数据类型都是object;
  • 像需要Array、String类型的对象(工厂模式思维满足需求);
  • 用new调用像是在创建对象,而不是像工厂模式一样直接调用一个函数;

例子1: 不能直接修改Array构造函数,想创建一个具有额外方法的特殊数组。
思考:不能直接修改即原型模式pass,数组类型构造函数默认输出为object?

		function SpecialArray () {
            let values= new Array();
            values.push.apply(values, arguments);
            values.toPipedString = function () {
            	return this.join('|');
            };
            return values;
        };
        let color = new SpecialArray('res', 'blue', 'green');
        alert(color.toPipedString()); // 'red|blue|green'
        

6.2.7 稳妥构造函数模式

定义:没有公共属性,而且其方法也不引用this的对象叫作稳妥对象。
使用场景:

  • 安全的环境,环境禁用this和new
  • 防止数据被其他应用程序改动(Mashup程序)
    “mashup是糅合,是当今网络上新出现的一种网络现象,将两种以上使用公共或者私有数据库的web应用,加在一起,形成一个整合应用。”

适用场景:

  • 新创建对象的实例方法不能引用this
  • 不使用new调用构造函数
		function Person (name, age) {
            var obj = new Object();
            // 私有变量和方法
            var name = name
            // 外部函数能访问到
            obj.age = age
            obj.sayName = function () {
                alert(name)
            }
            obj.sayAge = function () {
                alert(age)
            }
            return obj
        }

        let ff = new Person('dd', 23)
        ff.sayName() // dd
        ff.sayAge() // 23
        console.log(ff.name) // undefined
        console.log(ff.age) // 23

6.3 继承

继承缺陷
原型链
借用构造函数
组合继承
原型式继承
寄生式继承
寄生组合式继承
稳妥构造函数模式

继承

继承方式
接口继承继承方法签名(ECMAScript无法实现,由于函数没有签名)
实现继承继承实际的方法(利用原型链继承)

js中主要通过原型链实现继承;

6.3.1【原型链】

原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的;
基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法;
原型链的问题:对象实例共享所有的方法和属性,不能单独使用;
解决方法:借用构造函数;

例子1:
    function People(name){
        this.name = name;
    }
    People.prototype.say = function(){
        console.log('我会说话')
    }
    function Coder(money){
        this.money = money;
    }
    //继承了People
    Coder.prototype = new People;
    Coder.prototype.coding = function(){
    console.log('我会写编程')
    }
    let p2 = new Coder('10000');
    p2.coding()//我会写编程
    //继承了People的say公有属性
    p2.say()//我会说话
    console.log(p2)//Coder {money: "10000"}

6.3.2【借用构造函数】

借用构造函数,也叫(伪造对象或经典继承);
基本思想:在子类型的构造函数内部通过apply()或call()方法调用超类型构造函数;
解决问题:原型中包含引用类型值的问题;
缺点:函数无法复用;

    function People (name, sex, age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }
    People.prototype.say= function () {
        console.log('我会说话')
    }
    function Coder (money, name, sex, age) {
        // 继承了People,就会在即将创建的对象上执行People函数中所有对象初始化的代码,同时还传递了参数;
        People.call(this, name, sex, age)
        this.money = money;
    }
    Coder.prototype.coding = function () {
        console.log('我会敲代码')
    }
    let p1 = new Coder(10000, '张三', '男', '20');
    let p9 = new Coder(4000, 'momo', '女', '34');
    //每个对象都有自己不同的属性;
    console.log(p1) // Coder {name: "张三",  sex: "男",  age: "20",  money: 10000}
    console.log(p9) // Coder {name: "momo",  sex: "女",  age: "34",  money: 4000}
    // People函数中原型的方法,对Coder是不可见的;
    p1.say(); // Uncaught TypeError: p1.say is not a function
    p1.coding(); // 我会敲代码

6.3.3【组合继承模式】

组合继承模式,也叫伪经典模式;指的是将原型链和借用构造函数技术组合在一起。
思路:这种模式使用原型链继承共享发的方法和属性,并通过借用构造函数来实现对实例属性的继承;
即:使用原型链之后,在子类型构造函数的内部调用超(父)类型构造函数;这样,即通过在原型上定义方法或者属性实现了函数复用,同时又保证每个实例不仅可以拥有自己的属性;
instanceof和isPrototypeOf()可以识别;
例子1:

        function People(name,age,sex){
			this.name = name;
			this.age = age;	
			this.sex = sex;
		}

		People.prototype.say = function (){
			console.log('我会说话');	
		}


		function Coder(name,age,sex,money){
		 //借用构造函数继承People中每个实例自身的属性
			People.call(this,name,age,sex)
			this.money = money;
		}
        //原型链继承继承People原型上的共享方法;
		Coder.prototype = new People;

		Coder.prototype.coding = function (){
			console.log("我会敲代码");	
		}

		let c = new Coder('leo',30,"nan",10000);

		console.log(c);//Coder {name: "leo", age: 30, sex: "nan", money: 10000}

		c.say();//我会说话

		console.log(People.prototype);//{say: ƒ, constructor: ƒ}

6.3.4【原型式继承】

借助原型可以基于已有的对象创建对象,不必创建自定义类型。
本质:一个对象对另一个对象的浅复制;
适用:没有必要创建构造函数,只想让一个对象与另一个对象保持类似的情况,原型式继承完全可以继承;(注意包含引用值的属性会共享值)

		function obj(o) {
	        function F () {}
	        F.prototype = o;
	        return new F();
     	}
     	let person = {
     		name: 'mac',
     		friends: ['a', 'b', 'c']
     	};
     	let one = obj(person);
     	one.name = 'lenovo';
     	one.friends.push('d');
     	
		let two = obj(person);
		two.name = 'thinkpad';
		two.friends.push('e');
		alert(person.friends); // 'a,b,c,d,e'

拓展:ECMA5新增Object.create()规范原型式继承;在传入一个参数的情况下,Object.create()与obj()方法的行为相同
Object.create():
参数:2个
第一个–用作新对象原型的对象;
第二个–(可选的)一个为新对象定义额外属性的对象;与Object.defineProperties()方法的第二个参数格式相同;

		let person = {
     		name: 'mac',
     		friends: ['a', 'b', 'c']
     	};
     	let one = object.create(person);
     	one.name = 'lenovo';
     	one.friends.push('d');
     	
		let two = object.create(person);
		two.name = 'thinkpad';
		two.friends.push('e');
		alert(person.friends); // 'a,b,c,d,e'

6.3.5【寄生继承模式】

借助原型可以基于已有的对象创建对象。
本质:一个对象对另一个对象的浅复制;
适用:没有必要创建构造函数,只想让一个对象与另一个对象保持类似的情况,原型式继承完全可以继承;(注意包含引用值的属性会共享值)

6.3.6【寄生组合继承模式】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值