JS学习20高级技巧

高级函数

函数本质上是很简单且进程化的,但是由于JS天生的动态的特性,从使用方式上可以很复杂。

安全的类型检测

虽然JS中是有类型检测的,但是由于阅读器实现等它们其实不完全可靠。比如typeof在Safari中对正则表达式也返回function。
instanceof在存在多个全局作用域时也会把同种却不同作用域中构造函数的实例辨认为不同的实例:

var isArray = value instanceof Array;

这个表达式要是想返回true,value必须是个数组,且必须与Array构造函数在同1个全局作用域中,如果value是另外一个全局作用域中定义的数组,那这个表达式返回false。
检测某个对象是原生的还是开发人员自定义的对象时也会有问题。由于阅读器开始原生支持JSON了,而有些开发人员还是在用第3方库来实现JSON,这个库里会有全局的JSON对象,这样想肯定JSON对象是否是原生的就麻烦了。
解决这些问题的办法就是使用Object的toString方法,这个方法会返回1个[object NativeConstructorName]格式的字符串。

function isArray(value){ return Object.prototype.toString.call(value) == "[object Array]"; } function isFunction(value){ return Object.prototype.toString.call(value) == "[object Function]"; } function isRegExp(value){ return Object.prototype.toString.call(value) == "[object RegExp]"; }

不过要注意的是,对在IE中任何以COM情势实现的函数,isFunction()都会返回false。
对JSON是不是为原生的问题可以这样:

var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON) == "[object JSON]";

作用域安全的构造函数

之前我们说的构造函数是这么使用的:

function Person(name,age,job){ this.name = name; this.age = age; this.job = job; } var person = new Person("Nicholas",29,"Software Engineer");

在这里,由于使用了new操作符,this被绑定在了新创建的Person对象上,如果不用new操作符直接调用Person(),this就会被绑定到window上,这明显是不行的。

function Person(name,job){ if (this instanceof Person){ this.name = name; this.age = age; this.job = job; } else { return new Person(name,job); } } var person1 = Person("Nicholas","Software Engineer"); alert(window.name); //"" alert(person1.name); //"Nicholas" var person2 = new Person("Shelby",34,"Ergonomist"); alert(person2.name); //"Shelby"

不过在使用了这样作用域安全的构造函数后,如果使用基于构造函数盗取的继承,就会有问题:

function Polygon(sides){ if (this instanceof Polygon) { this.sides = sides; this.getArea = function(){ return 0; }; } else { return new Polygon(sides); } } function Rectangle(width,height){ //这里调用时,传进去的this是Rectangle类型的,没办法拓展side属性 Polygon.call(this,2); this.width = width; this.height = height; this.getArea = function(){ return this.width * this.height; }; } var rect = new Rectangle(5,10); alert(rect.sides); //undefined

解决方法就是使Rectangle也是Polygon的1个实例就好啦

Rectangle.prototype = new Polygon(); var rect = new Rectangle(5,10); alert(rect.sides); //2

惰性载入函数

由于阅读器差异,大量的判断阅读器能力的函数需要被使用(通常是大量的if),但是这些判断1般其实没必要每次都履行,在履行1次后,阅读器的能力就肯定了,以后就应当不用在判断了。比如:

function createXHR(){ if (typeof XMLHttpRequest != "undefined"){ return new XMLHttpRequest(); } else if (typeof ActiveXObject != "undefined"){ if (typeof arguments.callee.activeXString != "string"){ var versions = ["MSXML2.XMLHttp.6.0","MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],i,len; for (i=0,len=versions.length; i < len; i++){ try { new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; break; } catch (ex){ } } } return new ActiveXObject(arguments.callee.activeXString); } else { throw new Error("No XHR object available."); } }

这里的创建XHR对象的函数,每次创建对象时都会判断1次阅读器能力,这是没必要要的。
惰性载入有两种方式,第1种就是在函数第1次被调用时,根据不同情况,用不同的新函数把这个函数覆盖掉,以后调用就不需要再判断而是直接履行该履行的操作。

function createXHR(){ if (typeof XMLHttpRequest != "undefined"){ createXHR = function(){ return new XMLHttpRequest(); }; } else if (typeof ActiveXObject != "undefined"){ createXHR = function(){ if (typeof arguments.callee.activeXString != "string"){ var versions = ["MSXML2.XMLHttp.6.0",len; for (i=0,len=versions.length; i < len; i++){ try { new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; break; } catch (ex){ //skip } } } return new ActiveXObject(arguments.callee.activeXString); }; } else { createXHR = function(){ throw new Error("No XHR object available."); }; } return createXHR(); } createXHR(); alert(createXHR);

第2种思路1样,只不过是在声明函数时就指定新函数,不在第1次调用时再指定。两种办法其实本质上是1样的,看你想怎样用了。

var createXHR = (function(){ if (typeof XMLHttpRequest != "undefined"){ return function(){ return new XMLHttpRequest(); }; } else if (typeof ActiveXObject != "undefined"){ return function(){ if (typeof arguments.callee.activeXString != "string"){ var versions = ["MSXML2.XMLHttp.6.0",len=versions.length; i < len; i++){ try { new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; break; } catch (ex){ //skip } } } return new ActiveXObject(arguments.callee.activeXString); }; } else { return function(){ throw new Error("No XHR object available."); }; } })(); alert(createXHR);

函数绑定

函数绑定解决的问题是调用函数时函数的this对象被改成我们不想要的对象的问题。这类情况常常出现在指定事件处理函数时。看个例子:

var handler = { message: "Event handled",handleClick: function(event){ alert(this); alert(this.message); } }; var btn = document.getElementById("myButton"); EventUtil.addHandler(btn,"click",handler.handleClick); //[object HTMLButtonElement] undefined handler.handleClick(); // [object Object] Event handled

这里的this被改成了按钮元素。
解决办法就是将真实的函数套在1个闭包中,以此来保存着个函数的环境

var handler = { message: "Event handled",handleClick: function(event){ alert(this); // [object Object] alert(this.message); // Event handled } }; var btn = document.getElementById("myButton"); EventUtil.addHandler(btn,function(event){ alert(this); //[object HTMLButtonElement] handler.handleClick(event); });

可以看到,闭包的this被改成了button,而由于这个闭包的保护,我们的处理函数的环境保存住了。
为了不每次都手动创建1个闭包,我们可以创建1个工具函数bind:

function bind(fn,context){ alert(arguments[0]); //fn本身 return function(){ alert(arguments[0]); //event return fn.apply(context,arguments); }; }

这里就是把1个函数使用apply在特定的环境下调用,注意1下arguments对象,最后应当使用的是return过去的匿名函数(闭包)的arguments才对,这样事件给事件处理函数传递的参数才能穿到我们的函数里。

var handler = { message: "Event handled",handleClick: function(event){ alert(this); // [object Object] alert(this.message); // Event handled } }; var btn = document.getElementById("myButton"); EventUtil.addHandler(btn,bind(handler.handleClick,handler));

ES5中为所有函数都定义了1个原生的bind()方法。直接使用就行。

EventUtil.addHandler(btn,handler.handleClick.bind(handler));

只要是将某个函数指针以值的情势进行传递,同时该函数必须在特定环境中履行,被绑定函数的效果就显现了

函数柯里化

这个是用来创建已设置好1个或多个参数的函数,用1个例子看看基本思想:

function add(num1,num2){ return num1 + num2; } function curriedAdd(num2){ return add(5,num2); } alert(add(2,3)); //5 alert(curriedAdd(3)); //8

创建柯里化函数的通用方式:

function curry(fn){ var args = Array.prototype.slice.call(arguments,1); return function(){ var innerArgs = Array.prototype.slice.call(arguments); var finalArgs = args.concat(innerArgs); return fn.apply(null,finalArgs); }; }

这个函数主要的工作就是将外部函数和内部函数的参数都获得到传递给了返回的函数中。

function add(a,b) { alert(a); alert(b); alert(a+b); } var curriedAdd = curry(add,5); curriedAdd(3); //8 curriedAdd = curry(add,3); curriedAdd(5); //8 curriedAdd = curry(add,5,3); curriedAdd(); //8

可以将其利用在bind函数中来给事件处理函数传入多个参数。

function bind(fn,context){ var args = Array.prototype.slice.call(arguments,2); return function(){ var innerArgs = Array.prototype.slice.call(arguments); var finalArgs = args.concat(innerArgs); return fn.apply(context,finalArgs); }; } var handler = { message: "Event handled",handleClick: function(name,event){ alert(this.message + ":"+ name + ":"+ event.type); } }; var btn = document.getElementById("myButton"); EventUtil.addHandler(btn,handler,"myButton"));

这里要注意的是参数的顺序,比如这里name和event反了可就不对了呦。
ES5中的bind也实现了柯里化,直接使用就能够。

EventUtil.addHandler(btn,handler.handleClick.bind(handler,"myButton"));

防篡改对象

JS同享的本质使任意对象都可被随便修改。这样有时很不方便。ES5增加了几个方法来设置对象的行动。1旦将对象设置为防篡改就不能撤消了。

不可拓展对象

不可以添加新的属性和方法

var person = { name: "Nicholas" }; Object.preventExtensions(person); person.age = 29; alert(person.age); //undefined alert(Object.isExtensible(person)); //false person.name = "hahah"; alert(person.name); //hahah

密封的对象

不可以添加或删除属性,已有成员的[[Configurable]]被设置为false。

var person = { name: "Nicholas" }; Object.seal(person); person.age = 29; alert(person.age); //undefined delete person.name; alert(person.name); //"Nicholas" alert(Object.isExtensible(person)); //false alert(Object.isSealed(person)); //true

冻结的对象

不可拓展,密封,且对象数据属性的[[Writable]]将被设为false。如果定义了[[Set]],访问器属性仍然可写。

var person = { name: "Nicholas" }; Object.freeze(person); person.age = 29; alert(person.age); //undefined delete person.name; alert(person.name); //"Nicholas" person.name = "Greg"; alert(person.name); //"Nicholas" alert(Object.isExtensible(person));//false alert(Object.isSealed(person));//true alert(Object.isFrozen(person));//true

高级定时器

setTimeout()和setInterval()是很实用的功能,不过有些事情是要注意的。
JS是单线程的,这就意味着定时器实际上是很有可能被阻塞的。我们在这两个函数中所设置的定时,实际上是代表将代码加入到履行队列的事件,如果在加入时恰巧JS是空闲的,那末这段代码会立即被履行,也就是说这个定时被准时的履行了。相反,如果这时候JS其实不空闲或队列中还有别的优先级更高的代码,那就意味着你的定时器会被延时履行。

重复的定时器

使用setInterval创建定时器的目的是使代码规则的插入到队列中。这个方式的问题在于,存在这样1种可能,在上次代码还没履行完的时候代码再次被添加到队列。JS引擎会解决这个问题,在将代码添加到队列时会检查队列中有无代码实例,如果有就不添加,这确保了定时器代码被加入队列中的最小间隔是规定间隔。但是在某些特殊情况下还是会出现两个问题,某些间隔由于JS的处理被跳过,代码之间的间隔比预期的小。
所以尽可能使用setTimeout()摹拟间隔调用。

setTimeout(function(){ setTimeout(arguments.callee,interval); },interval);

Yielding Processes

如果你的页面中要进行大量的循环处理,每次循环会消耗大量的时间,那就会阻塞用户的操作。这时候分块处理数据就是个好办法。
这个例子每100ms取1个数组元素并添加到页面。

function chunk(array,process,context){ setTimeout(function(){ var item = array.shift(); process.call(context,item); if (array.length > 0){ setTimeout(arguments.callee,100); } },100); } var data = [12,123,1234,453,436,23,4123,45,346,5634,2234,345,342]; function printValue(item){ var div = document.getElementById("myDiv"); div.innerHTML += item + "<br>"; } chunk(data,printValue);

函数节流

这个是为了不某个操作连续不停的触发,比如触及到DOM操作,连续大量的DOM操作非常耗资源。
函数节流的基本思想是,每次调用实际上是设置1个真正调用的setTimeout操作,每次调用都会先清除当前的setTimeout再设置1个新的。如果短时间内大量调用,就回1直设置新的setTimeout而不履行setTimeout内的操作。只有停止调用足够长的时间,直到setTimeout时间到了,内部的真正操作才会履行1次。这个对onresize事件特别有用。

function throttle(method,context) { clearTimeout(method.tId); method.tId= setTimeout(function(){ method.call(context); },100); } function reDiv(){ var div = document.getElementById("myDiv"); div.innerHTML += "qqqqqq" + "<br>"; } window.onresize = function(){ throttle(reDiv); };

这样只有当你停下调剂窗口大小100ms后才会履行reDiv操作。

自定义事件

事件这样的交互其实就是视察者模式,这类模式由两类对象组成:主体和视察者。主体负责发布事件,视察者通过定阅这些事件来视察该主体。
创建自定义事件实际上就是创建1个管理事件的对象,并在里面存入各种事件类型的处理函数,触发事件时,只要你给失事件类型,这个对象就会找到相应的事件处理程序并履行。
下面是1个事件管理对象的大体情势:

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

添加事件处理程序时,addHandler会依照事件的类型将处理函数存入handlers属性中对应的数组里(如果还没有则新建)。
触发事件时使用fire,传入1个最少有type属性的对象。
使用时就像这样:

function handleMessage(event){ alert("Message received: " + event.message); } var target = new EventTarget(); target.addHandler("message",handleMessage); target.fire({ type: "message",message: "Hello world!"}); target.removeHandler("message",message: "Hello world!"});

自定义事件常常用来解耦对象之间的交互,使用事件就不需要有对象与对象之间的援用,使事件处理和事件触发保持隔离。

拖放

使用原始的鼠标事件

创建1个单例,使用模块模式来创建1个拖动的插件,返回两个方法,分别用来添加和移除所有的事件处理程序。

var DragDrop = function(){ var dragging = null; var diffX = 0; var 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; } break; case "mousemove": if (dragging !== null){ dragging.style.left = (event.clientX - diffX) + "px"; dragging.style.top = (event.clientY - diffY) + "px"; } break; case "mouseup": dragging = null; break; } }; return { enable: function(){ EventUtil.addHandler(document,"mousedown",handleEvent); EventUtil.addHandler(document,"mousemove","mouseup",handleEvent); },disable: function(){ EventUtil.removeHandler(document,handleEvent); EventUtil.removeHandler(document,handleEvent); } } }(); DragDrop.enable();

这样看来拖动的功能是实现了,不过有个问题。比如说这是我写的1个插件,使用的人想在拖动开始的时候做1些事情,那末他就不能不在起的源码里做出修改。他需要把所有要履行的代码和函数加到case “mousedown”里。如果这个插件我加密了呢,那想在这个时间点做些事情就更麻烦了。这样的做法明显其实不科学。
这时候如果使用了自定义事件,就能够很好的解决这个问题。

添加自定义事件

我们在这里新定义1个dragdrop变量,它是EventTarget类型的对象,在它上面我们可以添加事件处理函数或触发事件。在拖动开始时,进程中,结束时,都触发了自定义事件,这样有人想在这几个节点做甚么就直接添加事件处理函数就能够了。

var DragDrop = function(){ //这里的dragdrop是之前的EventTarget类型,可以用来保存和触发事件 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",y: event.clientY}); } break; case "mouseup": dragdrop.fire({type:"dragend",y: event.clientY}); dragging = null; break; } }; dragdrop.enable = function(){ EventUtil.addHandler(document,handleEvent); EventUtil.addHandler(document,handleEvent); }; dragdrop.disable = function(){ EventUtil.removeHandler(document,handleEvent); EventUtil.removeHandler(document,handleEvent); }; return dragdrop; }(); DragDrop.addHandler("dragstart",function(event){ var status = document.getElementById("myDiv"); status.innerHTML = "Started dragging " + event.target.id; }); DragDrop.addHandler("drag",function(event){ var status = document.getElementById("myDiv"); status.innerHTML += "<br/> Dragged " + event.target.id + " to (" + event.x + "," + event.y + ")"; }); DragDrop.addHandler("dragend",function(event){ var status = document.getElementById("myDiv"); status.innerHTML += "<br/> Dropped " + event.target.id + " at (" + event.x + "," + event.y + ")"; }); DragDrop.enable();

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


HTML代码中要想改变字体颜色,常常需要使用CSS样式表。CSS是一门用来描述网页上样式的语言,通过编写CSS代码可以实现网页中各元素的大小、颜色、字体等各种样式的控制。那么如何在HTML代码中应用CSS样式来改变字体颜色呢?这里为大家介绍一下。 首先,在HTML代码...
HTML代码如何让字体盖住图片呢?需要使用CSS的position属性及z-index属性。 img { position: relative; z-index: -1; } p { position: absolute; to...
HTML代码字体设置 在HTML中,我们可以使用标签来设置网页中的文字字体。常用的字体标签是font和style,下面我们来学习如何使用这些标签。 1. font标签 使用font标签可以改变文字的字体、颜色和大小。它有三个属性font-family、color和...
在网页设计中,HTML代码的字体和字号选择是非常重要的一个环节,因为它们直接关系到页面的可读性和视觉效果。 要指定文本的字体和字号,可以使用HTML中的样式属性。使用样式属性设置字体和字号,如下所示: <p style="font-family: Aria...
HTML(Hypertext Markup Language,超文本标记语言)是一种用于创建网页的标准语言。它由许多标签(一对尖括号包围的关键字)组成,这些标签告诉浏览器如何显示内容。使用HTML代码,我们可以轻松地创建各种类型的图像和图形,如太极图。 在HTM...
外链是指在一个网页中添加一个指向其他网站的链接,用户可以通过这个链接直接跳转到其他网站。在HTML中,实现外链的方法很简单,只需要使用标签就可以了。 <a href="http://www.example.com">这是一个外链,点击跳转到www.ex...
HTML代码是实现网页界面的基础,而网页中的各种表单则是用户和网站进行交互的重要方式之一。下面我们来介绍如何使用HTML代码实现一个简单的报名表格。 <form action="submit.php" method="post"> &lt...
HTML是一种标记语言,用于开发网站和其他互联网内容。字体是网站设计中的关键元素之一,它可以决定网站的整体风格和呈现效果。HTML提供了字体编辑器,使网站设计变得更加容易。 <font face="Arial"> 这里是Arial字体 &...
HTML代码中,字体样式是开发者们必备的一部分。在HTML中,我们可以通过特定的标签和属性设置字体的样式、颜色和大小,以达到更好的排版效果。 <p style="font-size: 14px; color: #333; font-family:...
HTML中的字体可以设为粗体,以强调文本信息。我们可以通过使用一些标签来实现这一功能。其中,常用的标签包括: 1. 标签:该标签会把文本加粗显示,语法如下: 这是一段加粗的文本 2. 标签:与标签作用相同,但语义更强,表示该文本内容的重要性。语法如下:...
HTML代码可以实现文件的上传和下载,在网页开发中相当常见。通过使用<input>标签和<form>标签,我们可以轻松创建一个文件上传表单。 <form action="upload.php" method="post" enct...
HTML代码非常常见于网页设计中。在一些需要处理时间相关数据的场景下,可能需要将时间戳转换为实际时间,这时候就需要使用一些特定的HTML代码。 function timeStamp2Time(time){ var date = new Date(time...
HTML是一种用于创建网页的标记语言。在HTML中,我们可以使用超链接标签实现下载文件到本地的功能。 具体实现步骤如下: <a href="文件的URL" download="文件名">下载文件</a> 其中,href属性是文件...
在HTML代码中,对于字体靠左对齐有各种方法。其中最简单的方式之一是使用pre标签。 使用pre标签可以保留一段文本中的空格和换行符,从而使代码排版更加整齐。下面是一个例子: <p>这是一个段落。</p> &lt...
HTML代码字典是一本解释HTML标记和属性的参考文献。这本字典中包含了最常用的HTML代码以及它们的属性和值的详细描述。 例如,以下是HTML代码字典中的一部分内容: <a href="url">link text</a> 在...
在网页开发过程中,遇到一些需要用户复制的内容,我们通常都会提供复制按钮,让用户更方便地复制所需内容。下面我们来介绍如何使用html代码实现一键复制的功能。 var copyBtn = document.querySelector('#copy-bt...
用户登录 欢迎来到公司登录界面,请输入用户名和密码登录 用户名: 密码: 代码解释: 第1行:定义了一个 HTML 文档 第2行:开始了 HTML 头部 第3行:定义了...
HTML 代码是用来创建网页的语言,它包含了许多不同的元素和属性,让我们可以在网页中添加各种不同的元素和内容,如文字、图片、链接等等。在编写 HTML 代码时,我们可以使用各种不同的样式来美化我们的网页,例如更改字体、颜色、大小等等。 font-family:...
HTML代码中的字体转移 在编写HTML代码时,我们经常会使用各种字体来增强页面的可读性和视觉效果。但是,有些字符或特殊符号可能会在HTML中具有不同的含义,这就需要使用字体转义转换成HTML可以正常显示的字符。 在HTML中,使用"&"符号表示一个特殊字符将要被转...
HTML 编程语言中,你可以使用字体属性来更改文本的字体大小、颜色和样式。其中,字体颜色是最常用的样式更改。在 HTML 中,你可以使用 "color" 属性来更改文本的颜色。下面是一个使用 "pre" 标签的代码示例,演示如何使用 "color" 属性来更改字体颜色...