JavaScript -- JavaScript高级程序设计
/* 基本类型 Undefined, Null, Boolean, Number, String. 复杂类型 Object 它是所有对象的基础类型。 引用类型 Object 创建:new Ojbect() 和 对象字面量语法{...} 读取:. 和 [...] Array 创建:new Array()、Array() 和 数组字面量语法[...] 读取:[下标] length:不只是只读,改变length的值,会改变数组的长度。 isArray() 判断某个对象是不是数组:https://www.cnblogs.com/fogwind/p/5884684.html toLocalString() 调用子项的toLocalString() toString() 调用子项的toString() valueOf() 返回数组 join() 参数指定间隔符,默认为逗号(,) push() 任意数量参数, 在数组末端增加项,返回新的长度 pop() 返回数组末端的值,数组长度减1 shift() 返回数组前端的值,数组长度减1 unshift() 任意数量参数,在数组前端增加项,返回新的长度 push + shift 模拟队列; unshift + pop 模拟反向队列 reverse() sort() 先调用数组项的toString()方法转换,再比较得到的字符串。可以传入比较函数来指定排序方式 slice(start, end) 返回[start, end)的数组项,通过指定负数,则从后计算。 splice(start, deleteNum, (add0, add1...)) 返回删除项组成的数组或空数组 indexOf(item(,startIndex)) 从前往后查找,返回第一个匹配项的下标或-1 lastIndexOf(item(,startIndex)) 从后往前查找,返回第一个匹配项的下标或-1 every(function(item, index, array) (, scope)) Boolean filter(function(item, index, array) (, scope)) Array forEach(function(item, index, array) (, scope)) void map(function(item, index, array) (, scope)) Array some(function(item, index, array) (, scope)) Boolean reduce(function(pre, cur, index, array) (, initValue)) reduceRight(function(pre, cur, index, array) (, initValue)) Date RegExp 创建:new RegExp(pattern, flags) 和 正则字面量 /pattern/flags 其中flags取值为g/i/m组合体 属性:global, ignoreCase, lastIndex, multiline, source exec() 即使在模式中指定了g,每次执行也只返回一个匹配项,可通过多次调用exec(),得到所有匹配项。没有匹配项,返回null. test() RegExp构造函数属性 Function 函数(是对象) 函数名 函数声明 function name(...) {...} 其实函数名仅仅是一个包含指针的变量而已,把它赋值给其他变量时,它们指向的是同一个函数对象。 被初始化为函数的变量 函数表达式 variable = function(...) {...} 将函数名想象为指针,更容易明白为什么没有函数重载了。 函数声明提升(function declaration hoisting) 函数内部属性:arguments, this 函数属性和方法: length, propotype, call(obj, arg1, arg2, ...) apply(obj, [arg1, arg2, ...]) ret = bind(obj, arg1, arg2, ...) bind返回的是一个函数,并不直接调用,需要再次调用:ret() 基本包装类型 每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象。 使用new操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保持在内存中,而自动创建的基本包装类型的对象, 则只存在于一行代码的执行瞬间,然后立即被销毁。举例: var s1 = "some text"; var s2 = s1.substring(2); 其中第2行代码相当与: var s1 = new String("some text"); var s2 = s1.substring(2); var s1 = null; 即第2行代码中s1.substring(2)存在一个自动转换,而自动转换的基本包装类型的对象在代码执行完之后立即被销毁了。 证明: var s1 = "some text"; s1.color = "red"; alert(s1.color); //undefined 以上代码中,color属性只是添加到了当前行自动创建的基本包装类型的对象上,赋值表达式执行完之后,对象被销毁。 在使用s1.color进行访问是,这时会产生一个新的基本包装类型的对象,当然也就没有color属性了。 Boolean Number toFixed(), toExponential(), toPrecision(), String charAt(index), charCodeAt(index), concat(str1, str2...), slice(start, end) 负数 + 长度; [start, end) substr(start, len) 负数1 + 长度; 负数2 --> 0; substring(index1, index2) 负数 --> 0; [min(index1, index2), max(index1, index2)) indexOf(str (, startIndex)), lastIndexOf(str (, startIndex)) trim(str) toLowerCase(), toLocaleLowerCase(), toUpperCase(), toLocaleUpperCase() match(pattern) 如果pattern指定了g,则返回所有匹配项组成的数组或者null; 如果没有指定g,则返回匹配项和捕获组,等同pattern.exec(str)的结果. search() replace() split() localeCompare() String.fromCharCode(cc1, cc2...) 单体内置对象 Global encodeURI(), encodeURIComponent(), decodeURI(), decodeURICompnent() eval() isNaN(), isFinite(), parseInt(), parseFloat() undefined, NaN, Infinity, Object, Array... Web浏览器中这个全局对象作为window对象的一部分加以实现。 Math min(num1, num2, num3...), max(num1, num2, num3...) //获取数组的最小值 var values = [1, 2, 3, 4]; var max = Math.min.apply(Math, values); ceil(), floor(), round() random() 随机取一个范围内的值 值 = Math.floor(Math.random() * 可能值的总数 + 第一个可能值) function selectFrom(lowerValue, upperValue) { var choices = upperValue - lowerValue + 1; return Math.floor(Math.random() * choices + lowerValue); } 对象 数据属性 设置 Object.defineProperty(object, property, {attribute: value(,...)}) Object.defineProperties(object, {property: {attribute: value}(,...)}) 读取 var descriptor = Object.getOwnPropertyDescriptor(object, property); alert(descriptro.attribute); 其中attribute可取:configurable/enumerable/writable/value 访问器属性 设置 Object.defineProperty(object, property, {attribute: value(,...)}) Object.defineProperties(object, {property: {attribute: value}(,...)}) 读取 var descriptor = Object.getOwnPropertyDescriptor(object, property); alert(descriptro.attribute); 其中attribute可取:configurable/enumerable/get/set 创建 工厂模式 function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var person1 = createPerson("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor"); 无法识别对象的类型,仅仅是object。 构造函数模式 function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); 每个实例都拥有自己的方法对象(Function的实例),其实这些方法做的是同样的事,完全可以共享一个方法对象,构造函数无法 实现此目的。 原型模式 function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); person1.sayName(); //"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true 函数(Person) 每一个函数在创建时,都会有一个prototype属性,prototype指向函数的原型对象。 函数的原型对象(Person Prototype) 默认情况下,所有原型对象都会自动获得一个constructor属性,constructor指向函数。 函数的实例(person1, person2) 每个对象都有一个属性__proto__([[Prototype]])指向函数的原型对象。 isPrototypeOf() alert(Person.prototype.isPrototypeOf(person1)); //true getPrototypeOf() alert(Object.getPrototypeOf(person1) == Person.prototype); //true alert(Object.getPrototypeOf(person1).name); //"Nicholas" 代码读取某个对象的属性时,先在当前实例查找,找到直接返回,如果没找到,则沿着__proto__查找。也就是说, 对象可以共享原型链上的属性和方法。 但是,如果直接通过对象给对象本身不存在但是在原型链中存在的属性赋值时,并不会修改原型链中属性的值,而是会在对象 中增加一个相应的属性。比如: person1.name = "Greg"; alert(person1.name); //"Greg"—— 来自实例 屏蔽了原型中的name属性 alert(person2.name); //"Nicholas"—— 来自原型 通过delete 删除属性后又可以访问到原型中的属性了 delete person1.name; alert(person1.name); //"Nicholas"—— 来自原型 hasOwnProperty() 判断实例本身是否存在某属性 alert(person1.hasOwnProperty("name")); //false in操作符 判断对象是否能否访问某一属性 alert("name" in person1); //true //判断对象是否能访问原型中的某一属性 function hasPrototypeProperty(object, name){ return !object.hasOwnProperty(name) && (name in object); } person1.name = "Greg"; alert(hasPrototypeProperty(person, "name")); //false for-in操作 返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,包括对象本身和原型链上的属性,相同属性只会出现一次。 Object.keys(object) 返回对象本身可枚举的属性。 Object.getOwnPropertyNames(object) 返回对象本身所有的属性,无论是否可枚举。 更简单的原型语法 function Person(){ } Person.prototype = { name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } }; 但是这样,整个原型对象就没能自动获得constructor属性了。 var friend = new Person(); alert(friend instanceof Object); //true alert(friend instanceof Person); //true alert(friend.constructor == Person); //false alert(friend.constructor == Object); //true 可以手动添加,只是constructor的[[Enumerable]]特性就变为true了,可以使用Object.defineProperty(...)处理。 function Person(){ } Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } }; //重设构造函数,只适用于 ECMAScript 5 兼容的浏览器 Object.defineProperty(Person.prototype, "constructor", { enumerable: false, value: Person }); 原型的动态性 function Person(){ } var friend = new Person(); Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } }; friend.sayName(); //error 对原型的修改会影响在修改之前已经创建好的实例。friend的__proto__属性指向的还是原来的原型对象。 再通过friend instanceof Person 来判断时,结果是false. 原型对象的问题 function Person(){ } Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", friends : ["Shelby", "Court"], // 引用类型 sayName : function () { alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van" alert(person2.friends); //"Shelby,Court,Van" alert(person1.friends === person2.friends); //true 修改引用类型的值会反映到所有实例中。 组合使用构造函数模式和原型模式 (广泛使用) 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 person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Count,Van" alert(person2.friends); //"Shelby,Count" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性 动态原型模式 (非常完美) function Person(name, age, job){ //属性 this.name = name; this.age = age; this.job = job; //方法 if (typeof this.sayName != "function"){ Person.prototype.sayName = function(){ alert(this.name); }; } } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName(); 寄生构造函数模式 function Person(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas" 建议在可以使用其他模式的情况下,不要使用这种模式 稳妥构造函数模式 function Person(name, age, job){ //创建要返回的对象 var o = new Object(); //可以在这里定义私有变量和函数 //添加方法 o.sayName = function(){ alert(name); }; //返回对象 return o; } var friend = Person("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas" 一是新创建对象的实例方法不引用 this;二是不使用 new 操作符调用构造函数。 继承 原型链 function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } //继承了 SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function (){ return this.subproperty; }; var instance = new SubType(); alert(instance.getSuperValue()); //true 借用构造函数 (也叫做伪造对象或经典继承) function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ //继承了 SuperType SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green" 组合继承 (最常用的继承模式) function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name, age){ //继承属性 SuperType.call(this, name); this.age = age; } //继承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); }; var instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" instance1.sayName(); //"Nicholas"; instance1.sayAge(); //29 var instance2 = new SubType("Greg", 27); alert(instance2.colors); //"red,blue,green" instance2.sayName(); //"Greg"; instance2.sayAge(); //27 使用原型链实现对原型的属性和方法的继承,使用构造函数实现对实例属性的继承。 原型式继承 function object(o) { function F(){}; F.prototype = o; return new F(); } 本质上object()对传入的对象进行了一次浅复制。 var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = object(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); alert(person.friends); //"Shelby,Court,Van,Rob,Barbie" 存在引用类型问题 var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = Object.create(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = Object.create(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); alert(person.friends); //"Shelby,Court,Van,Rob,Barbie" ECMAScript 5 新增的Object.create 功能与object()类似。 寄生式继承 function createAnother(original){ var clone = object(original); //通过调用函数创建一个新对象 clone.sayHi = function(){ //以某种方式来增强这个对象 alert("hi"); }; return clone; //返回这个对象 } var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi(); //"hi" 新增了自己的方法(sayHi),这种模式函数不能进行复用,每个实例都有自己的副本。 寄生组合式继承 (引用类型最理想的继承范式) function inheritPrototype(subType, superType){ var prototype = object(superType.prototype); //创建对象 prototype.constructor = subType; //增强对象 subType.prototype = prototype; //指定对象 } function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name, age){ SuperType.call(this, name); this.age = age; } inheritPrototype(SubType, SuperType); //使用寄生组合模式,这样不必调用SuperType的构造函数 SubType.prototype.sayAge = function(){ alert(this.age); }; 整个过程只要调用一次SuperType的构造函数,而组合继承模式要调用两次,会在SubType.prototype创建不必要的属性。 typeof 判断基本类型 var result = typeof variable; result undefined, boolean, string, number, object, function 注:null --> Object instanceof 判断引用类型 var result = variable instanceof constructor; result true, false alert(person instanceof Object); // 变量 person 是 Object 吗? alert(colors instanceof Array); // 变量 colors 是 Array 吗? alert(pattern instanceof RegExp); // 变量 pattern 是 RegExp 吗? instanceof通过判断对象沿着原型链能否找到类型的prototype属性指向的原型对象。 作用域链与原型链 var name = 'window'; var age = '1'; function Person(name, age) { this.age = 2; this.sayName = function(greet) { var sex = 'male'; //person1.sayName()调用时,会形成三个作用域,使用Chrome跟踪可以看到,分别是:Local, Closure, Global. //在Local中有greet, sex, this, 三个变量, Closure有name变量,Global中也有name变量。 //当直接访问某一个变量名时,代码会沿着Local, Closure, Global的顺序查找 //比如greet是在Local中找到,name在Closure中找到 //当要在方法中访问this.age时,这时候,才是在Local的this变量中,沿着this对象的原型链进行查找。 console.log(greet + ' ' + name + ' ' + age); // Hello Greg 28 console.log(this.age); // 2 console.log(arguments[0]); // Hello } //测试多层作用域 this.sayName2 = function() { var name2 = name; return function() { var sex = 'male'; console.log(name2); // Greg console.log(this.age); // 1 console.log(arguments[0]); // undefined } } } // function Person(nameP, age) { // this.age = age; // this.name = nameP; // this.sayName = function(greet) { // var sex = 'male'; // console.log(greet + ' ' + name); // } // } var person1 = new Person('Greg', 28); var person2 = new Person('Nico', 29); person1.sayName('Hello'); var say2 = person1.sayName2(); say2(); //person1.sayName.call(this, 'Hi'); 作用域链看函数的[[Scope]]属性,函数在执行时,会添加本地作用域到作用域链前端。 在Chrome中作用域链体现为:Local--Closure(--Closure)--Global 严格模式 执行环境(execution context)、变量对象(variable object)、作用域链(scope chain) 垃圾收集 标记清除(mark-and-sweep) 引用计数(reference counting) 存在循环引用问题 解除引用(dereferencing) variable = null; 通过这种方式解除循环引用和全局变量的引用(局部变量会在离开执行环境时自动被解除引用)。 解除引用的值,其内存会在垃圾收集器下次运行时回收。 //使用||操作符提供默认值backupObject. var myObject = preferredObject || backupObject; '' null undefined 十进制 前导零 十六进制 数字+字符 字符 Number() 0 0 NaN 十进制 去掉 十进制 NaN NaN parseInt() NaN NaN NaN 十进制 去掉 十进制 数字 NaN parseFloat() NaN NaN NaN 十进制 去掉 0 数字 NaN parseInt 可以指定转换的进制,当指定了进制时,会按指定的进制解析字符串。 for (property in expression) statement; 通过for-in循环输出的属性名的顺序是不可预测的,所有的属性都会被返回一次,但先后顺序不同浏览器可能会有差异。 switch (expression) expression可以是任何数据类型,比较时使用的时全等(===)比较。 function (arg0, arg1...) { statements; } 函数命名的参数只是提供便利,并不是必须的。 函数的参数由arguments对象保存,它是一个类似数组的对象(并不是Array的实例),它的值的数量取决于调用函数时传递的参数数量, 而不取决于函数定义时命名的参数数量。 没有传递值的命名参数将自动被赋予undefined值。 不存在函数签名的特性,因此函数没有重载,后定义的会覆盖前面定义的同名函数。可以在函数内部, 通过arguments,取得传入参数的数量和类型,进而实现类重载功能。 返回值没有类型限定,同一个函数可以有多种类型的返回值。 函数名本身就是变量,指向一个函数对象。 函数声明与函数表达式 函数声明存在 函数提升 //不要这样做! if(condition){ function sayHi(){ alert("Hi!"); } } else { function sayHi(){ alert("Yo!"); } } 函数表达式 //可以这样做 var sayHi; if(condition){ sayHi = function(){ alert("Hi!"); }; } else { sayHi = function(){ alert("Yo!"); }; } 递归 function factorial(num){ if (num <= 1){ return 1; } else { return num * factorial(num-1); } } var anotherFactorial = factorial; factorial = null; alert(anotherFactorial(4)); //出错! factorial不在是函数了,所以报错 function factorial(num){ if (num <= 1){ return 1; } else { return num * arguments.callee(num-1); } } 使用arguments.callee,但是在严格模式下不能访问arguments.callee var factorial = (function f(num){ if (num <= 1){ return 1; } else { return num * f(num-1); } }); 使用函数表达式,这样f始终存在。 闭包 指有权访问另一个函数作用域的变量的函数。 闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存,同时还有可能导致内存泄漏。 函数声明 --> 函数表达式 (function(){ //这里是块级作用域 })(); 定义一个匿名函数,然后立即执行,这样在函数内部形成了一个块级作用域, 在这个作用域内可以声明任意变量而不用担心与全局作用域里的变量冲突。 私有变量 function MyObject(){ //私有变量和私有函数 var privateVariable = 10; function privateFunction(){ return false; } //特权方法 this.publicMethod = function (){ privateVariable++; return privateFunction(); }; } 静态私有变量 (function(){ //私有变量和私有函数 var privateVariable = 10; function privateFunction(){ return false; } //构造函数 MyObject = function(){ }; //公有/特权方法 MyObject.prototype.publicMethod = function(){ privateVariable++; return privateFunction(); }; })(); 模块模式 var singleton = function(){ //私有变量和私有函数 var privateVariable = 10; function privateFunction(){ return false; } //特权/公有方法和属性 return { publicProperty: true, publicMethod : function(){ privateVariable++; return privateFunction(); } }; }(); 增强模块模式 (可以知道singleton的具体类型) var singleton = function(){ //私有变量和私有函数 var privateVariable = 10; function privateFunction(){ return false; } //创建对象 var object = new CustomType(); //添加特权/公有属性和方法 object.publicProperty = true; object.publicMethod = function(){ privateVariable++; return privateFunction(); }; //返回这个对象 return object; }(); 事件 事件冒泡,事件捕获 "DOM2级事件"规定的事件流:事件捕获阶段、处于目标阶段和事件冒泡阶段。 DOM0级事件处理程序:domObj.onXXX = function() {...}; 移除:onXXX = null; DOM2级事件处理程序: domObj.addEventListener("XXX", function() {...}, true/false); true:捕获阶段调用事件处理程序;false:冒泡阶段调用事件处理程序。 移除:domObj.removeEventListener("XXX", handler, true/false); remov...参数必须以add参数一样,因此处理函数必须定义到一个变量,匿名函数无法被移除。 IE事件处理程序:(冒泡阶段调用处理程序) domObj.attachEvent("onXXX", function() {...}); 移除:domObj.detachEvent("onXXX", handler); 资料 YUI http://developer.yahoo.com/yui/ 基于Canvas的图像过滤器:http://www.html5rocks.com/en/tutorials/canvas/imagefilters/ webgl学习:http://learningwebgl.com/blog/?page_id=1217 http://www.hiwebgl.com/?p=42 */ /** * 跨浏览器的事件处理程序 */ var EventUtil = { addHandler: function(element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false); } else if (element.attachEvent) { element.attachEvent("on" + type, handler); } else { element["on" + type] = handler; } }, getEvent: function(event) { return event ? event : window.event; }, getTarget: function(event) { return event.target || event.srcElement; }, preventDefault: function(event) { if (event.preventDefault) { event.preventDefault(); } else { event.returnValue = false; } }, removeHandler: function(element, type, handler) { if (element.removeEventListener) { element.removeEventListener(type, handler, false); } else if (element.detachEvent) { element.detachEvent("on" + type, handler); } else { element["on" + type] = null; } }, stopPropagation: function(event) { if (event.stopPropagation) { event.stopPropagation(); } else { event.cancelBubble = true; } } }; /** * 自定义事件 */ 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 (var i = 0, len = handlers.length; i < len; i++) { handlers[i](event); } } }, removeHandler: function(type, handler) { if (this.handlers[type] instanceof Array) { var handlers = this.handlers[type]; for (var i = 0, len = handlers.length; i < len; i++) { if (handlers[i] === handler) { break; } } handlers.splice(i, 1); } } } var DragDrop = function() { var dragdrop = new EventTarget(), dragging = null; diffX = 0; diffY = 0; function handleEvent(event) { event = EventUtil.getEvent(event); var target = EventUtil.getTarget(event); switch(event.type) { case "mousedown": if (target.className.indexOf("draggable") > -1 ) { 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: "drag", target: dragging, x: event.clientX, y: event.clientY}); } break; case "mouseup": if (dragging !== null) { dragdrop.fire({type: "dragend", target: dragging, x: event.clientX, y: event.clientY}); dragging = null; } break; } } dragdrop.enable = function() { EventUtil.addHandler(document, "mousedown", handleEvent); EventUtil.addHandler(document, "mousemove", handleEvent); EventUtil.addHandler(document, "mouseup", handleEvent); }; dragdrop.disable = function() { EventUtil.removeHandler(document, "mousedown", handleEvent); EventUtil.removeHandler(document, "mousemove", handleEvent); EventUtil.removeHandler(document, "mouseup", handleEvent); }; return dragdrop; }(); DragDrop.addHandler("dragstart", function(event) { var status = document.getElementById("status"); status.innerHTML = "Started dragging " + event.target.id; }); DragDrop.addHandler("drag", function(event) { var status = document.getElementById("status"); status.innerHTML += "<br/> Dragged " + event.target.id + " to (" + event.x + ", " + event.y + ")"; }); DragDrop.addHandler("dragend", function(event) { var status = document.getElementById("status"); status.innerHTML += "<br/> Dropped " + event.target.id + " at (" + event.x + ", " + event.y + ")"; }); DragDrop.enable();