22. 高级技巧

JavaScript是一种极其灵活的语言,具有多种使用风格。一般来说,要么使用过程方式,要么使用面向对象方式。
然而,由于其天生的动态属性,JavaScript还能使用更复杂和有趣的编程模式。

22.1 高级函数

  • 安全的类型检测
    • Object.prototype.toString.call() --> [object Array]
    • typeof & instanceof
  • 作用域安全的构造函数
    • 构造函数被当做普通函数调用, 非严格模式下this指向window
    • 构造函数内部进行类型检测判断
    • 继承时会出问题
      • this对象并非是Person实例,所以会创建一个新的Person对象返回
      • 返回值并没有被接受
      • 而且,this对象也没有被增强
function Person(name, age, job){
    console.log('this:' + Object.getPrototypeOf(this).constructor.name);
	if(this instanceof Person){
		this.name = name;
		this.age = age;
		this.job = job;
	}else{
		return new Person(name, age, job);
	}
}

function worker(name, age, job, salaries){
	Person.call(this,name, age, job);
	this.salaries = salaries;
}
worker.prototype = new Person();

不使用原型继承的话:

使用原型继承的话:

  • 惰性载入函数
    • 每次都做判断对性能浪费,实际上只需做一次就可
    • 惰性载入表示函数执行的分支仅会发生一次
    • 核心是:自我毁灭式更新
// 初始版本
function createXHR(){
	if(typeof XMLHttpRequest != "undefined"){
		return new XMLHttpRequest();
	}else if (typeof ActiveXObject != "undefined"){
		return new ActiveXObject();
	}else{
		throw new Error("No XHR object available");
	}
}
// 改进一,函数声明覆盖
function createXHR(){
	if(typeof XMLHttpRequest != "undefined"){
		createXHR = function(){
			return new XMLHttpRequest();
		}
	}else if (typeof ActiveXObject != "undefined"){
		createXHR = function(){
			return new ActiveXObject();
		}
	}else{
		createXHR = function(){
			throw new Error("No XHR object available");
		}
	}
	return createXHR();
}
// 改进二,函数表达式IIFE
var createXHR = (function(){
	if(typeof XMLHttpRequest != "undefined"){
		return function(){
			return new XMLHttpRequest();
		}
	}else if (typeof ActiveXObject != "undefined"){
		return function(){
			return new ActiveXObject();
		}
	}else{
		return function(){
			throw new Error("No XHR object available");
		}
	}
	return createXHR();
})();
  • 函数绑定
    • 方法函数被当做普通函数调用
    • 函数绑定要创建一个函数,可以在特定的this环境中以指定参数调用另一个函数
    • 该技巧常常和回调函数与事件处理程序一起使用
    • 作用: 在将函数当做变量传递的同时保留代码执行环境
// 方法调用
var handler = {
	message: "Event Handled",
	handleClick: function(event){
		console.log(this.message);
	}
}
// 传递方法,会改变this指向
button.addEventListener("click", handler.handleClick);
// 使用闭包改进
button.addEventListener("click", function(event){
	handler.handleClick(event); // 方法调用
})
// 自定义bind函数
function bind(_fun, _this){
	return function(){
		return _fun.apply(_this, arguments);
	}
}
// ES5 bind
button.addEventListener("click", handler.handleClick.bind(handler));
  • 函数柯里化
    • 与函数绑定紧密相关的话题是函数柯里化function currying,它用来创建已经设定好了一个或多个参数的函数。
// 概念理解
function add(a,b){
	return a + b;
}
function addFive(x){
	return add(5, x);
}
// 动态创建(柯里工厂)
function curry(fun){
	var args = Array.prototype.slice.call(arguments, 1); // fun参数
	return function(){
		var innerArgs = Array.prototype.slice.call(arguments); // 内部参数
		var finalArgs = args.concat(innerArgs); // 合并参数
		return fun.apply(null, finalArgs);
	}
}
// 绑定执行环境
function curry(fun, _this){
	var args = Array.prototype.slice.call(arguments, 2); // fun参数
	return function(){
		var innerArgs = Array.prototype.slice.call(arguments); // 内部参数
		var finalArgs = args.concat(innerArgs); // 合并参数
		return fun.apply(_this, finalArgs);
	}
}
// ES5 bind
var handler = {
	message: "Event Handled",
	handleClick: function(name, event){
		console.log(this.message + ":" + name + ":" + event.type);
	}
}
button.addEventListener("click", handler.handleClick.bind(handler, "mybtn"));

柯里化结果测试:

22.2 防篡改对象tamper-proof object

  • 不可扩展对象
    • 不能为对象本身添加新的属性,但可以给原型添加
    • Object.preventExtensions(obj) 静默失败
    • Object.isExtensible(obj) true/false
  • 密封对象
    • sealed object
    • 1.不可扩展; 2.[[Configurable]] == false(不能删除属性和方法)
    • Object.seal(obj)
    • Object.isSealed(obj)
  • 冻结对象
    • frozen object
    • 1.密封; 2.[[Writable]] == false (不能改变值)
    • Object.freeze(obj)
    • Object.isFrozen(obj)

三者是递进的,包含的,越来越严格的。从不能添新不能删旧,再到不能更改已有

22.3 高级定时器

  • 时间到了就把代码提交到任务队列,并不是立即执行
  • 使用链式setTimeout代替setInterval
  • yielding Processes
    • 数组分块array chunking --> 分割循环,防止阻塞,避免长时间运行脚本错误
  • 函数节流:某些代码不可以在没有间断的情况下连续重复执行
    • 场景:输入框change时查询匹配项
var processor = {
	timeoutId: null,
	performProcessing: function(){
		// 实际处理代码
	},
	process: function(){
		clearTimeout(this.timeoutId);
		var that = this;
		this.timeoutId = setTimeout(function(){
			that.performProcessing();
		}, 100)
	}
}
// 执行
processor.process();

// throttle节流
function throttle(method, _this){
	clearTimeout(method.tId);
	method.tId = setTimeout(function(){
		method.call(_this);
	})
}

22.4 自定义事件

事件是一种叫作观察者的设计模式,这是一种创建松散耦合代码的技术。
对象可以发布事件,用来表示对象生命周期中某个有趣的时刻到了。
然后其他对象可以观察该对象,等待有趣时刻的到来并通过运行代码来响应。
观察者模式有两类对象组成:主体观察者。主体负责发布事件,同时观察者通过订阅这些事件来观察该主体。
该模式的一个关键概念是主体并不知道观察者的任何事情,主体可以独自存在并正常运作,即使观察者不存在。

function EventTarget(){
	this.handlers = {};
}
EventTarget.prototype = {
	constructor: EventTarget,
	addHandler: function(type, handler){
		if(typeof this.handlers[type] == "undefined"){
			this.handlers[type] = [];
		}
		this.handlers[type].push(handler);
	},
	fire: function(event){
		if(!event.target){
			event.target = this;
		}
		if(this.handlers[event.type] instanceof Array){
			var handlers = this.handlers[event.type];
			for(let i = 0; i < handlers.length; i++){
				handlers[i](event);
			}
		}
	},
	removeHandler: function(type, handler){
		if(this.handlers[type] instanceof Array){
			var handlers = this.handlers[type], index = 0;
			for(;index < handlers.length; index ++){
				if(handlers[type] === handler){
					break;
				}
			}
			this.handlers[type].splice(index, 1);
		}
	}
}

function handleMessage(event){
	console.log("Message:" + event.message);
}
var target = new EventTarget();
// 订阅
target.addHandler("message", handleMessage);
// 发布
target.fire({type: "message", message: 'hello world'});

// 继承
function Person(name, age){
	EventTarget.call(this);
	this.name = name;
	this.age = age;
}
var prototype = Object.create(EventTarget.prototype);
prototype.constructor = Person;
Person.prototype = prototype;
Person.prototype.say = function(msg){
	this.fire({type: "message", message: msg});
}
// 实例
var p = new Person('zpj', 24);
p.addHandler("message", function(event){console.log(event.type + ":" + event.message)});
p.say("How are you?");

22.5 拖放

  • position: absolute; --> top/left
  • mousedown --> mousemove --> mouseup
  • 左上角 —> 鼠标捕捉点,看起来更平滑
var DragDrop = function(){
	// 模块模式,单例对象,闭包隐藏实现细节
	var dragdrop = new EventTarget(),
		dragging = null,
		diffX = 0,
		diffY = 0;
		
	function handleEvent(event){
		var target = event.target
		switch (event.type){
			case "mousedown":
				if(target.classList.contains("draggable")){
					dragging = target;
					diffX = event.clientX - target.offsetLeft;
					diffY = event.clientY - target.offsetTop;
					dragdrop.fire({type: "dragstart", target: dragging, x: event.clientX, y: event.clientY})
				}
				break;
			case "mousemove":
				if(dragging !== null){
					dragging.style.left = (event.clientX - diffX) + "px";
					dragging.style.top = (event.clientY - diffY) + "px";
					dragdrop.fire({type: "dragging", target: dragging, x: event.clientX, y: event.clientY})
				}
				break;
			case "mouseup":
				dragdrop.fire({type: "dragend", target: dragging, x: event.clientX, y: event.clientY})
				dragging = null;
				break;
		}
	}
	// 公共接口
	dragdrop.enable = function(){
		document.addEventListener("mousedown", handleEvent);
		document.addEventListener("mousemove", handleEvent);
		document.addEventListener("mouseup", handleEvent);
	}
	dragdrop.disable = function(){
		document.removeEventListener("mousedown", handleEvent);
		document.removeEventListener("mousemove", handleEvent);
		document.removeEventListener("mouseup", handleEvent);
	}
	return dragdrop;
}
// 实战
var div = document.createElement("div");
div.style.height = "50px";
div.style.width = "50px";
div.style.background = "red";
div.style.position = "absolute";
div.style.cursor = "grab";
div.classList.add("draggable");
document.body.appendChild(div);
var drag = DragDrop();
drag.enable();