Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

面向对象、闭包、高阶函数 #15

Open
isNeilLin opened this issue Nov 28, 2017 · 0 comments
Open

面向对象、闭包、高阶函数 #15

isNeilLin opened this issue Nov 28, 2017 · 0 comments
Labels
JS JavaScript内容

Comments

@isNeilLin
Copy link
Owner

isNeilLin commented Nov 28, 2017

Chapter1 - 面向对象的Javascript

Javascript通过原型委托的方式实现继承。

动态类型语言的变量类型要到程序运行的时候,待变量被赋予某个值之后,才会具有某种类型。

鸭子类型(duck typing):“如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。”

var duck = {
    duckSinging: function(){
        console.log( '嘎嘎嘎' );
    }
};

var chicken = {
    duckSinging: function(){
        console.log( '嘎嘎嘎' );
}
};

var choir = [];    // 合唱团

var joinChoir = function( animal ){
    if ( animal && typeof animal.duckSinging === 'function' ){
        choir.push( animal );
        console.log( '恭喜加入合唱团' );
        console.log( '合唱团已有成员数量:' + choir.length );
    }
};

joinChoir( duck );    // 恭喜加入合唱团
joinChoir( chicken );    // 恭喜加入合唱团

多态: 同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。多态的思想实际上是把“做什么”和“谁去做”分离开来。要实现这一点,归根结底要先消除类型之间的耦合关系。

“JavaScript对象的多态性是与生俱来的: “JavaScript的变量类型在运行期是可变的。一个JavaScript对象,既可以表示Duck类型的对象,又可以表示Chicken类型的对象。某一种动物能否发出叫声,只取决于它有没有makeSound方法,而不取决于它是否是某种类型的对象,这里不存在任何程度上的类型耦合。”

var makeSound = function (animal){
     animal.sound();
}
var Duck = function(){}
Duck.prototype.sound = function(){
     console.log('嘎嘎嘎')
}
var Chicken = function(){}
Chicken.prototype.sound = function(){
     console.log('咯咯咯')
}
makeSound(new Duck()); // 嘎嘎嘎
makeSound(new Chicken()); // 咯咯咯

封装

封装的目的是将信息隐藏。Javascript只能依赖变量的作用域来实现封装特性。
封装包括:封装数据封装实现、封装类型、封装变化

原型模式和基于原型继承的JavaScript对象系统

在以类为中心的面向对象编程语言中,对象总是从类中创建而来。而在原型编程的思想中,一个对象是通过克隆另一个对象得到的。

原型模式的实现关键是语言本身是否提供了clone方法。ECMAScript 5提供了Object.create方法可以用来克隆对象。

var Plane = function(){
     this.blood = 50;
     this.attackLevel = 1;
    this.defenseLevel = 1;
}
var plane = new Plane();
plane.blood = 500;
plane.attackLevel = 10;
plane.defenseLevel = 7;

var clonePlane = Object.create(plane);
console.log(clonePlane)  // 输出:Object {blood: 500, attackLevel: 10, defenseLevel: 7}

// 兼容IE8
Object.create = Object.create || function(obj){
    var F = function() {};
    F.prototype = obj;
    return new F();
}

原型模式的真正目的并非在于需要得到一个一模一样的对象,而是提供了一种便捷的方式去创建某个类型的对象,克隆只是创建这个对象的过程和手段。

原型编程范型至少包括以下基本规则

  • 所有的数据都是对象
  • 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它
  • 对象会记住原型
  • 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型。

基于原型链的委托机制就是原型继承的本质

JavaScript中的原型继承

JavaScript中的根对象是Object.prototype对象,Object.prototype对象是一个空的对象。我们在JavaScript遇到的每个对象,实际上都是从Object.prototype对象克隆而来的,Object.prototype对象就是它们的原型。

var obj1 = new Object();
var obj2 = {};

console.log( Object.getPrototypeOf( obj1 ) === Object.prototype );    // 输出:true
console.log( Object.getPrototypeOf( obj2 ) === Object.prototype );    // 输出:true

“在JavaScript语言里,我们并不需要关心克隆的细节,因为这是引擎内部负责实现的。我们所需要做的只是显式地调用var obj1 = new Object()或者var obj2 = {}。此时,引擎内部会从Object.prototype上面克隆一个对象出来,我们最终得到的就是这个对象。”

var Person = function(name){
    this.name = name;
}
Person.prototype.getName = function(){
    return this.name;
}
var p = new Person('sven');

console.log(p.name) // sven
console.log(p.getName) // sven
console.log(Object.getPrototypeOf(p) === Person.prototype) // true

就JavaScript的真正实现来说,其实并不能说对象有原型,而只能说对象的构造器有原型。对于“对象把请求委托给它自己的原型”这句话,更好的说法是对象把请求委托给它的构造器的原型。

Javascript给对象提供了一个__proto__的隐藏属性,某个对象的__proto__属性会指向它的构造器的原型对象。即{Constructor}.prototype

对象构造器的原型并不仅限于Object.prototype上,而是可以动态的指向其他对象。这样一来,当对象a需要借用对象b的能力时,可以有选择性地把对象a的构造器的原型指向对象b,从而达到继承的效果

var obj = {name: 'sven'};
var A = function(){}
A.prototype = obj;
var a = new A();
console.log(a.name); // sven

当我们期望得到一个“类”继承自另外一个“类”的效果时,往往会用下面的代码来模拟实现

var A = function(){}
A.prototype = {name: 'sven'};
var B = function(){}
B.prototype = new A()
var b = new B();
console.log(b.name) // sven

ECMAScript 6带来了新的Class语法。这让JavaScript看起来像是一门基于类的语言,但其背后仍是通过原型机制来创建对象。

class Animal {
    constructor(name){
        this.name = name;
    }
    getName(){
    		return this.name;
    }
}

class Dog extends Animal {
	constructor(name){
		super(name)
	}
	speak(){
		return "woof";
	}
}

var dog = new Dog("Scan");
console.log(dog.getName()+" says: "+ dog.speak());

chapter2-this、call和apply

javascript的this总是指向一个对象。具体指向哪个对象是在运行时基于函数的执行环境动态绑定的。this的指向大致可以分为四种:

  • 作为对象的方法调用
  • 作为普通函数调用
  • 构造器调用
  • Function.prototype.callFunction.prototype.apply调用

作为对象的方法调用

当函数作为对象的方法被调用时,this指向该对象。

var obj = {
	a: 1,
	getA: function(){
		alert ( this === obj );    // 输出:true
  		alert ( this.a );    // 输出: 1
	}
}

作为普通函数调用

当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的this总是指向全局对象。

在ECMAScript 5的strict模式下,这种情况下的this已经被规定为不会指向全局对象,而是undefined

function func(){
    "use strict"
    alert ( this );    // 输出:undefined
}

func();

构造器调用

构造器的外表跟普通函数一模一样,它们的区别在于被调用的方式。当用new运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象。

如果构造器显式地返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,而不是我们之前期待的this。如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,就不会造成这个问题。

var MyClass = function(){
	this.name = 'sven';
	return {
		name: 'anne'
	}
}
var obj = new MyClass();
console.log(obj.name); // 'anne'

Function.prototype.callFunction.prototype.apply调用

Function.prototype.callFunction.prototype.apply可以动态地改变传入函数的this.

var obj1 = {
    name: 'sven',
    getName: function(){
        return this.name;
    }
};

var obj2 = {
    name: 'anne'
};

console.log( obj1.getName() );     // 输出: sven
console.log( obj1.getName.call( obj2 ) );    // 输出:anne

call和apply

apply接受两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply方法把这个集合中的元素作为参数传递给被调用的函数

var func = function( a, b, c ){
    alert ( [ a, b, c ] );    // 输出 [ 1, 2, 3 ]
};
func.apply( null, [ 1, 2, 3 ] ); 

call传入的参数数量不固定,跟apply相同的是,第一个参数也是代表函数体内的this指向,从第二个参数开始往后,每个参数被依次传入函数

var func = function( a, b, c ){
    alert ( [ a, b, c ] );    // 输出 [ 1, 2, 3 ]
};

func.call( null, 1, 2, 3 );

当使用call或者apply的时候,如果我们传入的第一个参数为null,函数体内的this会指向默认的宿主对象。但如果是在严格模式下,函数体内的this还是为null

使用callapply借用其他对象的方法

Math.max.apply(null,[6,4,8,1,2,7])  // 8

call和apply的实际用途

改变this指向

var obj1 = {
    name: 'sven'
};

var obj2 = {
    name: 'anne'
};

window.name = 'window';

var getName = function(){
    alert ( this.name );
};

getName();    // 输出: window
getName.call( obj1 );    // 输出: sven
getName.call( obj2 );    // 输出: anne

Function.prototype.bind

模拟Function.prototype.bind的实现

Function.prototype.bind = function(){
	var self = this,
	context = [].shift.call(arguments),
	args = [].slice.call(arguments);
	return function(){
		return self.apply(context,[].concat.call(args, [].slice.call(arguments)))
	}
}

var obj = {
	name: 'sven'
}

var func = function(a,b,c,d){
	console.log(this.name)  // sven
	console.log([a,b,c,d]) // [1,2,3,4]
}.bind(obj,1,2);

func(3,4)

借用其他对象的方法

(function(){
    Array.prototype.push.call( arguments, 3 );
    console.log ( arguments );    // 输出[1,2,3]
})( 1, 2 );

闭包

封装变量

var mult = (function(){
	var cache = {};
	return function(){
		var args = [].join.call(arguments,',');
		if(cache[args]){
			return cache[args]
		}
		var a = 1;
		for(var i = 0, l = arguments.length; i < l ; i++ ){
			a = a* arguments[i];
		}
		return cache[args] = a;
	}
})()

console.log(mult(1,2,3)) // 6
console.log(mult(1,2,3)) // 6

提炼函数是代码重构中的一种常见技巧。如果在一个大函数中有一些代码块能够独立出来,我们常常把这些代码块封装在独立的小函数里面。独立出来的小函数有助于代码复用,如果这些小函数有一个良好的命名,它们本身也起到了注释的作用。如果这些小函数不需要在程序的其他地方使用,最好是把它们用闭包封闭起来。

var mult = (function(){
	var cache = {};
	var calculate = function(){
		var a = 1;
		for(var i = 0, l = arguments.length; i < l ; i++ ){
			a = a* arguments[i];
		}
		return a;
	}
	return function(){
		var args = [].join.call(arguments,',');
		if(cache[args]){
			return cache[args]
		}
		return cache[args] = calculate.apply(null,arguments);
	}
})()

延续局部变量的寿命

使用img进行数据上报

var report = (function(){
	var imgs = [];
	return function(){
		var img = new Image();
		imgs.push(img);
		img.src = src;
	}
})()

面向对象实现命令模式

<html>
	<body>
		<button id="execute">execute</button>
		<button id="undo">undo</button>
		<script>
			var Tv = {
				open: function(){
					console.log('open tv');
				},
				close: function(){
					console.log('close tv');
				}
			}

			var OpenTvCommand = function(receiver){
				this.receiver = receiver;
			}
			OpenTvCommand.prototype.execute = function(){
				this.receiver.open();
			}
			OpenTvCommand.prototype.close = function(){
				this.receiver.close();
			}
			
			var setCommand = function(command){
				document.getElementById('execute').onclick = function(){
					command.execute();
				}
				document.getElementById('undo').onclick = function(){
					command.close();
				}
			}
			
			setCommand(new OpenTvCommand(Tv))
			
		</script>
	</body>
</html>

判断一个数据是否是数组,在以往的实现中,可以基于鸭子类型的概念来判断,比如判断这个数据有没有length属性,有没有sort方法或者slice方法等。但更好的方式是用Object.prototype.toString来计算。Object.prototype.toString.call( obj )返回一个字符串,比如Object.prototype.toString.call( [1,2,3] )总是返回"[object Array]",而Object.prototype.toString.call( "str")总是返回"[object String]"。

var isType = function(type){
	return function(obj){
		return Object.prototype.toString.call(obj) === '[object '+type+']';
	}
}

var isString = isType('String');
var isNumber = isType('Number');
var isArray = isType('Array');

console.log(isArray([1,2,3,4]));  // true

JavaScript实现AOP(面向切面编程)

AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。

Function.prototype.before = function(beforeFn){
	var __self = this; 
	return function(){
		beforeFn.apply(this,arguments);
		return __self.apply(this,arguments);
	}
}

Function.prototype.after = function(afterFn){
	var __self = this; 
	return function(){
		var ret = __self.apply(this,arguments);
		afterFn.apply(this,arguments);
		return ret;
	}
}

var func = function(){
    console.log( 2 );
};

func = func.before(function(){
    console.log( 1 );
}).after(function(){
    console.log( 3 );
});**JavaScript实现AOP(面向切面编程)**

AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。

```javascript
Function.prototype.before = function(beforeFn){
	var __self = this; 
	return function(){
		beforeFn.apply(this,arguments);
		return __self.apply(this,arguments);
	}
}

Function.prototype.after = function(afterFn){
	var __self = this; 
	return function(){
		var ret = __self.apply(this,arguments);
		afterFn.apply(this,arguments);
		return ret;
	}
}

var func = function(){
    console.log( 2 );
};



func();

函数柯里化

currying又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

var curring = function(fn){
	var args = [];
	return function(){
		if(arguments.length===0){
			fn.apply(this,args)
		}else{**JavaScript实现AOP(面向切面编程)**

AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通过“动态织入”的方式掺入业务逻辑模块中。

```javascript
Function.prototype.before = function(beforeFn){
	var __self = this; 
	return function(){
		beforeFn.apply(this,arguments);
		return __self.apply(this,arguments);
	}
}

Function.prototype.after = function(afterFn){
	var __self = this; 
	return function(){
		var ret = __self.apply(this,arguments);
		afterFn.apply(this,arguments);
		return ret;
	}
}

var func = function(){
    consol
			[].push.call(args,arguments);
			return arguments.callee;
		}
	}
}

var cost = (function(){
    var money = 0;

    return function(){
        for ( var i = 0, l = arguments.length; i < l; i++ ){
            money += arguments[ i ];
        }
        return money;
    }

})();

var cost = currying( cost );
cost( 100 );    // 未真正求值
cost( 200 );    // 未真正求值
cost( 300 );    // 未真正求值

console.log( cost() );     // 求值并输出:600

反柯里化(uncurrying)的实现

Function.prototype.uncurrying = function(){
	var self = this; // self就是Array.prototype.push
	return function(){
		// Array.prototype.push当做Function.prototype.call的this传进去
		return Function.prototype.call.apply(self,arguments)
		// arguments是[obj,'first'],apply接受数组形式的参数
		// 接下来被直接传入Function.prototype.call,call接受以逗号分隔的N个参数的形式
		// 所以arguments中的第一个参数会被当做call里面的this,其他参数一次传入call
		// 整句代码相当于 Array.prototype.push.call(obj,'first');
	}
}

var push = Array.prototype.push.uncurrying();
var obj = {
	length: 1,
	a: 'test'
};
push( obj, ‘first’ );

函数节流

var throttle = function(fn,interval){
	var self = fn, 
		 timer, firstTime = true;
	return function(){
		var _this = this, args = arguments;
		if(firstTime){
			self.apply(_this, args)
			return firstTime = false;
		}
		if(timer){
			return false;
		}
		timer = setTimeout(function(){
			clearTimeout(timer);
			timer = null;
			self.apply(_this,args)
		},interval||500)
	}
}
@isNeilLin isNeilLin changed the title Javascript设计模式与开发实践-读书笔记 Javascript设计模式与开发实践-读书笔记01 Nov 29, 2017
@isNeilLin isNeilLin changed the title Javascript设计模式与开发实践-读书笔记01 面向对象、作用域、闭包、高阶函数 Nov 30, 2017
@isNeilLin isNeilLin added JS JavaScript内容 and removed 读书笔记 labels Jun 18, 2021
@isNeilLin isNeilLin changed the title 面向对象、作用域、闭包、高阶函数 面向对象、闭包、高阶函数 Jun 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JS JavaScript内容
Projects
None yet
Development

No branches or pull requests

1 participant