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();