这里有新鲜出炉的 Javascript 教程,程序狗速度看过来!
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
这篇文章主要为大家详细介绍了 javascript 函数中的 3 个高级技巧, 具有一定的参考价值,感兴趣的小伙伴们可以参考一下
前面的话
函数对任何一门语言来说都是一个核心的概念,在 javascript 中更是如此。前面曾以深入理解函数系列的形式介绍了函数的相关内容,本文将再深入一步,介绍函数的 3 个高级技巧
技巧一:作用域安全的构造函数
构造函数其实就是一个使用 new 操作符调用的函数
- function Person(name, age, job) {
- this.name = name;
- this.age = age;
- this.job = job;
- }
- var person = new Person('match', 28, 'Software Engineer');
- console.log(person.name); //match
如果没有使用 new 操作符,原本针对 Person 对象的三个属性被添加到 window 对象
- function Person(name, age, job) {
- this.name = name;
- this.age = age;
- this.job = job;
- }
- var person = Person('match', 28, 'Software Engineer');
- console.log(person); //undefined
- console.log(window.name); //match
window 的 name 属性是用来标识链接目标和框架的,这里对该属性的偶然覆盖可能会导致页面上的其它错误,这个问题的解决方法就是创建一个作用域安全的构造函数
- function Person(name,age,job){
- if(this instanceof Person){
- this.name=name;
- this.age=age;
- this.job=job;
- }else{
- return new Person(name,age,job);
- }
- }
- var person=Person('match',28,'Software Engineer');
- console.log(window.name); // ""
- console.log(person.name); //'match'
- var person= new Person('match',28,'Software Engineer');
- console.log(window.name); // ""
- console.log(person.name); //'match'
但是,对构造函数窃取模式的继承,会带来副作用。这是因为,下列代码中,this 对象并非 Polygon 对象实例,所以构造函数 Polygon() 会创建并返回一个新的实例
- function Polygon(sides){
- if(this instanceof Polygon){
- this.sides=sides;
- this.getArea=function(){
- return 0;
- }
- }else{
- return new Polygon(sides);
- }
- }
- function Rectangle(wifth,height){
- Polygon.call(this,2);
- this.width=this.width;
- this.height=height;
- this.getArea=function(){
- return this.width * this.height;
- };
- }
- var rect= new Rectangle(5,10);
- console.log(rect.sides); //undefined
如果要使用作用域安全的构造函数窃取模式的话,需要结合原型链继承,重写 Rectangle 的 prototype 属性,使它的实例也变成 Polygon 的实例
- function Polygon(sides){
- if(this instanceof Polygon){
- this.sides=sides;
- this.getArea=function(){
- return 0;
- }
- }else{
- return new Polygon(sides);
- }
- }
- function Rectangle(wifth,height){
- Polygon.call(this,2);
- this.width=this.width;
- this.height=height;
- this.getArea=function(){
- return this.width * this.height;
- };
- }
- Rectangle.prototype= new Polygon();
- var rect= new Rectangle(5,10);
- console.log(rect.sides); //2
技巧二:惰性载入函数
因为各浏览器之间的行为的差异,我们经常会在函数中包含了大量的 if 语句,以检查浏览器特性,解决不同浏览器的兼容问题。比如,我们最常见的为 dom 节点添加事件的函数
- function addEvent(type, element, fun) {
- if (element.addEventListener) {
- element.addEventListener(type, fun, false);
- }
- else if(element.attachEvent){
- element.attachEvent('on' + type, fun);
- }
- else{
- element['on' + type] = fun;
- }
- }
每次调用 addEvent 函数的时候,它都要对浏览器所支持的能力进行检查,首先检查是否支持 addEventListener 方法,如果不支持,再检查是否支持 attachEvent 方法,如果还不支持,就用 dom0 级的方法添加事件。这个过程,在 addEvent 函数每次调用的时候都要走一遍,其实,如果浏览器支持其中的一种方法,那么他就会一直支持了,就没有必要再进行其他分支的检测了。也就是说,if 语句不必每次都执行,代码可以运行的更快一些。
解决方案就是惰性载入。所谓惰性载入,指函数执行的分支只会发生一次
有两种实现惰性载入的方式
【1】第一种是在函数被调用时,再处理函数。函数在第一次调用时,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了
我们可以用下面的方式使用惰性载入重写 addEvent()
- function addEvent(type, element, fun) {
- if (element.addEventListener) {
- addEvent = function (type, element, fun) {
- element.addEventListener(type, fun, false);
- }
- }
- else if(element.attachEvent){
- addEvent = function (type, element, fun) {
- element.attachEvent('on' + type, fun);
- }
- }
- else{
- addEvent = function (type, element, fun) {
- element['on' + type] = fun;
- }
- }
- return addEvent(type, element, fun);
- }
在这个惰性载入的 addEvent() 中,if 语句的每个分支都会为 addEvent 变量赋值,有效覆盖了原函数。最后一步便是调用了新赋函数。下一次调用 addEvent() 时,便会直接调用新赋值的函数,这样就不用再执行 if 语句了
但是,这种方法有个缺点,如果函数名称有所改变,修改起来比较麻烦
【2】第二种是声明函数时就指定适当的函数。 这样在第一次调用函数时就不会损失性能了,只在代码加载时会损失一点性能
以下就是按照这一思路重写的 addEvent()。以下代码创建了一个匿名的自执行函数,通过不同的分支以确定应该使用哪个函数实现
- var addEvent = (function () {
- if (document.addEventListener) {
- return function (type, element, fun) {
- element.addEventListener(type, fun, false);
- }
- }
- else if (document.attachEvent) {
- return function (type, element, fun) {
- element.attachEvent('on' + type, fun);
- }
- }
- else {
- return function (type, element, fun) {
- element['on' + type] = fun;
- }
- }
- })();
技巧三:函数绑定
在 javascript 与 DOM 交互中经常需要使用函数绑定,定义一个函数然后将其绑定到特定 DOM 元素或集合的某个事件触发程序上,绑定函数经常和回调函数及事件处理程序一起使用,以便把函数作为变量传递的同时保留代码执行环境
- <button id="btn">
- 按钮
- </button>
- <script>
- var handler = {
- message: "Event handled.",
- handlerFun: function() {
- alert(this.message);
- }
- };
- btn.onclick = handler.handlerFun;
- </script>
上面的代码创建了一个叫做 handler 的对象。handler.handlerFun() 方法被分配为一个 DOM 按钮的事件处理程序。当按下该按钮时,就调用该函数,显示一个警告框。虽然貌似警告框应该显示 Event handled,然而实际上显示的是 undefiend。这个问题在于没有保存 handler.handleClick() 的环境,所以 this 对象最后是指向了 DOM 按钮而非 handler
可以使用闭包来修正这个问题
- <button id="btn">
- 按钮
- </button>
- <script>
- var handler = {
- message: "Event handled.",
- handlerFun: function() {
- alert(this.message);
- }
- };
- btn.onclick = function() {
- handler.handlerFun();
- }
- </script>
当然这是特定于此场景的解决方案,创建多个闭包可能会令代码难以理解和调试。更好的办法是使用函数绑定 一个简单的绑定函数 bind() 接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去
- function bind(fn,context){
- return function(){
- return fn.apply(context,arguments);
- }
- }
这个函数似乎简单,但其功能是非常强大的。在 bind() 中创建了一个闭包,闭包使用 apply() 调用传入的函数,并给 apply() 传递 context 对象和参数。当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数
- <button id="btn">
- 按钮
- </button>
- <script>
- function bind(fn, context) {
- return function() {
- return fn.apply(context, arguments);
- }
- }
- var handler = {
- message: "Event handled.",
- handlerFun: function() {
- alert(this.message);
- }
- };
- btn.onclick = bind(handler.handlerFun, handler);
- </script>
ECMAScript5 为所有函数定义了一个原生的 bind() 方法,进一步简化了操作。
只要是将某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就突显出来了。它们主要用于事件处理程序以及 setTimeout() 和 setInterval()。然而,被绑定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用。
来源: http://www.phperz.com/article/17/0519/331677.html