javascript进阶笔记

夜月天      2022-02-12     111

关键词:

js是一门函数式语言,因为js的强大威力依赖于是否将其作为函数式语言进行使用。在js中,我们通常要大量使用函数式编程风格。函数式编程专注于:少而精、通常无副作用、将函数作为程序代码的基础构件块。

在函数式编程中,有一种函数称为匿名函数,也就是没有名称的函数,是js中的一个非常重要的概念。通常匿名函数的使用情况是,创建一个供以后使用的函数。比如将匿名函数保存在一个变量里面,或将其作为一个对象方法,更有甚者将其作为一个回调等等之类的。

//保存在变量中,通过fn去引用
var fn=function(){return true;};

//对象方法
var obj={
    fn:function(){return ture;} 
};

//作为返回值
function method(){
    return function(){return true;};
}


function method2(callback){
    return callback();
}

//作为回调
method2(function(){return true;});

尽管匿名函数没有名称,但是其在不同的时机都是可以调用的。对于匿名函数,有一个小细节值得注意的是,将其赋值给一个变量,例如var fn=function(){},不要认为这个变量fn就是这个匿名函数的名称,fn只是一个引用该函数的变量而已,请不要误解。上一篇文章讲过,一个函数声明包含四个部分:

1、function 关键字;

2、函数名称,为可选。没有名称的函数称为匿名函数;

3、圆括号包含的一个以逗号分隔的参数列表,该参数列表可以为空,但是圆括号必须存在;

4、大括号包含的函数体。函数体可以为空,但大括号必须存在。

匿名函数没有名称,如果我们在将它赋值给变量的时候,给它声明一个名称呢,可以吗?当然可以。不过这时,这个匿名函数就变成了内联函数,这个内联函数名称只能在其自身函数中使用。有一个递归的例子就说明了这种情况:

//求和函数。sum是该内联函数名称,该内联函数名称只能在其自身函数内部使用。
var fn=function sum(n){
    return n>1?sum(n-1)+n:1;
}

//alert(sum(10)); 这种方式使用是错误的--sum未定义
alert(fn(4));//10

当函数调用自身,或调用另外一个函数,但这个函数在调用其他函数的某个地方又调用了自己时,递归就发生了。

我们还可以使用匿名函数来进行递归调用。如:

//使用匿名函数进行递归调用
var myMath={
    sum:function(n){
        return n>1?this.sum(n-1)+n:1;
    }
};


//将匿名函数加上一个函数名称,变为内联函数,这种方式定义的递归比较安全
var myMath2={
    sum:function add(n){
        return n>1?add(n-1)+n:1;
    }
};

将匿名函数加上一个函数名称,变为内联函数,并使用该函数名称定义递归,这种方式定义的递归函数比直接用对象方法定义的要安全得多。因为一个进行递归调用的对象属性引用,与函数的实际名称不同,这种引用可能是暂时的,这种依赖方式会导致混乱。如:

//使用匿名函数进行递归调用
var myMath={
    sum:function(n){
        return n>1?this.sum(n-1)+n:1;
    }
};

var myMath2={
    sum2:myMath.sum //直接引用myMath的sum方法
};

//将myMath对象清空
myMath={};

try{
    alert(myMath2.sum2(4));

}catch(e){
    alert(e.message);
}

//结果:this.sum is not a function

为什么会出现this.sum不是一个函数的错误呢? 因为我们通过myMath2对象去调用的sum2方法,而sum2方法引用的是myMath的sum方法,在sum方法内部的this现在指向的是myMath2对象,而myMath2对象没有sum方法。记住,当一个函数作为方法被调用时,函数上下文,也就是this参数,指的是调用该方法的对象。如果对this参数还有不明白的地方,请去看上一篇文章javascript进阶笔记(1) 。在上面的代码中,我们使用内联函数的名称进行递归是很安全的,虽然myMath对象已经被清空了,但是myMath的sum方法依然可以被访问,这是闭包的作用。

尽管可以给内联函数进行命名,但这些名称只能在函数自身内部才是可见的。也就是说,内联函数的名称,它们的作用域仅限于声明它们的函数。

 

js中的函数与其他语言中的函数不同,js赋予了函数很多特性,其中最重要的特性之一就是将函数作为第一型对象。函数可以有属性,也可以有方法,可以分配给变量和属性,也可以享有所有普通对象所拥有的特性,而且还有一个超级特性,它们可以被调用!!!这句话非常重要!我们可以给js中的函数设置某些属性,让函数拥有状态和缓存记忆。如下代码:

//给函数设置id属性
var store={
    nextId:1,
    cache:{},
    add:function(fn){
        if(!fn.id)
            fn.id=this.nextId++;
            return !!(this.cache[fn.id]=fn);
    }
};
//素数判断
//如果该函数的属性缓存中有该素数,则从缓存在取,否则就需要进行计算后将结果存于缓存中,并将结果返回。
function isPrime(value){
   if(!isPrime.result)
        isPrime.result={};
   if(isPrime.result[value]!=null)
        return isPrime.result[value];
   
   var prime=value!=1;
   for(var i=0;i<value;i++){
        if(value%i==0){
            prime=false;
            break;
        }
   }
   
   return isPrime.result[value]=prime;
}

缓存记忆有两个优点:

1、享有性能优势。直接从以前计算的结果集合中取出结果,如果没有,就计算后将结果保存在缓存中;

2、发生在幕后,无需用户或开发人员做任何特殊操作或为此做任何额外的初始化工作。

缺点:

1、牺牲内存。

 

接下来我们实现一个伪数组对象,不错,就是伪造一个数组。代码如下:

var elems={
    length:0,//实现一个数组必须的属性,集合中元素的个数
    add:function(elem){
        Array.prototype.push.call(this,elem);
    },
    gather:function(elem){
        this.add(elem);
    },
    delete:function(){
        Array.prototype.pop.call(this);
    }
};

本来Array这个构造器的原型对象prototype就有关于操作数组的方法,我们可以直接拿来用,不要觉得不要意思,都是自家东西,哈哈!Array.prototype.push方法是通过其函数上下文操作自身数组的。每次调用push方法,程序将会增加length的属性值,然后给对象添加一个数字属性,并将其引用到传入的元素上。

 

在js中,在函数的实际调用中,我们可以给函数传递一个可变的实际参数列表,也就是说,js灵活且强大的特性之一就是函数可以接受任意数量的参数。我们通过Math对象的max和min方法来举例。如下代码:

var myMath={
    //求数组中的最大值  array数组的元素个数是可变的
    max:function(array){
        return Math.max.apply(Math,array);
    },
    //求数组中的最小值  array数组的元素个数是可变的
    min:function(array){
        return Math.min.apply(Math,array);
    }
};

var maxValue=myMath.max([3,2,5,6,1]);//6
maxValue=myMath.max([5,8]);//8

因为js中没有针对数组求最大值和最小值的方法,因此我们可以自己定义。举此例的目的,是为了接下来引入函数重载的概念。

 

js不像其他语言一样,可以进行函数的重载。在js中,没有函数重载的方式。那么在js中还能进行函数的重载吗?当然可以!不过需要绕一个弯。

所有的函数都有一个length属性,这个属性等于该函数在声明时需要传入形参的个数。请不要将函数的length属性与arguments的length属性混淆,arguments的length属性是实际传入参数的个数。

因此,对于一个函数,在参数方面,我们可以确定两件事情:

1、通过函数的length属性,可以知道该函数声明了多少个形参;

2、通过arguments.length,可以知道函数在调用时传入了多少个实参。

我们可以利用以上参数个数的差异化来实现函数的重载。

首先,来看通过传入参数的个数去执行不同的操作。如果想要冗长且完整的函数,可以像如下定义重载:

var persons={
    find:function(){
        switch(arguments.length){
            case 0:
                /*do something*/
                break;
            case 1:
                 /*do something*/
                break;
            case 2:
                 /*do something*/
                break;          
            default:
                 /*do something*/
                break; 
        }
    }
};

在这种方式中,通过arguments参数获取实际传入的参数个数进行判断,每一种情况都会执行不同的操作。但是这种判断方式不是很整洁,也比较冗长。当然,有的时候也需要做这样的操作。

在此,我们可以换一种思路,通过一个addMethod函数来给persons对象重载find方法。代码如下:

function addMethod(object,methodName,fn){
        //将旧方法保存下来,在old.apply那一步会依次循环 匹配实参与形参的个数是否相等。循环最重要的事情是 每调用一次addMethod方法,就会将fn和old变量保存在当前对应的闭包中
        var old=object[methodName];
        //重载该对象的方法
        object[methodName]=function(){
            //如果该匿名函数的形参个数和实参个数匹配,就调用该函数
            if(fn.length==arguments.length)
                return fn.apply(this,arguments);
            //如果传入的参数不匹配,则调用原有参数的方法
            else if(typeof old =='function')
                return old.apply(this,arguments);
        };
    }
    
    var persons={};
    //给persons对象创建一个find方法
    addMethod(persons,"find",function(){
        //为了方便调试,就直接返回形参的个数,
        return 0;
    });
    //给persons对象重载一个find方法
    addMethod(persons,"find",function(name){
        return 1;
    });
    //给persons对象重载一个find方法
    addMethod(persons,"find",function(first,last){
        return 2;
    });
    
    //接下来调用persons的find方法
    alert(persons.find());//0
    alert(persons.find('sj'));//1
    alert(persons.find(1,4));//2

看一下浏览器对persons的find方法的解析:

这是一个绝佳的技巧,因为这些绑定函数实际上并没有存储于任何典型的数据结构中,而是在闭包里作为引用进行存储。

在使用这种特定技巧的时候,需要注意以下几点:

1、这种重载方式只适用于不同数量的参数,但并不区分类型、参数名称或其他东西;

2、这种重载方式会有一些函数调用的开销,我们要考虑在高性能时的情况。

 

javascript进阶--慕课网学习笔记

            JAVASCRIPT—进阶篇给变量取个名字(变量命名)变量名字可以任意取,只不过取名字要遵循一些规则:1.必须以字母、下划线或美元符号开头,后面可以跟字母、下划线、美元符号... 查看详情

javascript进阶学习笔记笔记

基础知识:1)函数的声明方式:普通的声明方式;functionmyFun(m,n)alert(m+n);使用变量初始化函数;VarmyFun=function(m,n)alert(m+n);使用构造函数;varmyFun=newfunction(‘m’,’n’,’alert(m 查看详情

javascript进阶笔记

js是一门函数式语言,因为js的强大威力依赖于是否将其作为函数式语言进行使用。在js中,我们通常要大量使用函数式编程风格。函数式编程专注于:少而精、通常无副作用、将函数作为程序代码的基础构件块。在函数式编程中... 查看详情

javascript进阶笔记

本篇文章我们来学习和讨论一下js中的闭包。闭包是纯函数式编程的一个特性,因为它们能够大大简化复杂的操作。在js中,闭包的重要性不言而喻!简单的说,闭包(closure)是一个函数在创建时允许该自身函数访问并操作该自身... 查看详情

javascript进阶篇——(javascript内置对象---下)--math对象---笔记整理

Math对象使用Math的属性和方法:<scripttype="text/javascript">varmypi=Math.PI;varmyabs=Math.abs(-15);document.write(mypi);document.write(myabs);</script>运行结果:3.14159265358979315Math对象是一个固有的对象,无需创建它,直接把 查看详情

javascript进阶笔记

本篇文章我们来学习和讨论一下js中的闭包。闭包是纯函数式编程的一个特性,因为它们能够大大简化复杂的操作。在js中,闭包的重要性不言而喻!简单的说,闭包(closure)是一个函数在创建时允许该自身函数访问并操作该自身... 查看详情

javascript进阶篇——(dom—节点---属性访问节点)—笔记整理

节点属性在文档对象模型(DOM)中,每个节点都是一个对象。DOM节点有三个重要的属性:  1.nodeName:节点的名称  2.nodeValue:节点的值  3.nodeType:节点的类型一、nodeName属性:节点的名称,是只读的。  1.元素节点的nodeName与... 查看详情

javascript进阶篇——(dom—节点---插入删除和替换元素创建元素创建文本节点)—笔记整理

插入节点appendChild()在指定节点的最后一个子节点列表之后添加一个新的子节点。语法:appendChild(newnode)//参数://newnode:指定追加的节点。为ul添加一个li,设置li内容为PHP,代码如下:1<!DOCTYPEHTML>2<html>3<head>4<metahttp-equ... 查看详情

vue学习笔记进阶篇——render函数

...用template来创建你的HTML。然而在一些场景中,你真的需要JavaScript的完全编程的能力,这就是 render函数,它比template更接近编译器。<h1><aname="hello-world"href="#hello-world">Helloworld!& 查看详情

学习笔记js进阶语法一dom基础

内容整理自《从0到1Javascript快速上手》下半部分-进阶语法篇 getElementById()和getElementsByTagName()区别:1、 getElementById()获取的是一个元素,而getElementsByTagName()获取的是多个元素,即“类数组”(也成伪数组)... 查看详情

学习笔记js进阶语法一window对象(代码片段)

内容整理自《从0到1Javascript快速上手》下半部分-进阶语法篇示例:打开/关闭新窗口<!DOCTYPEhtml><html> <head> <metacharset="utf-8"> <title>打开窗口/关闭窗口</title> <script> window.onload=function... 查看详情

hvie进阶笔记

--mysql方式createtableaccount_channel(account_String,channel_String)asselecta.account,b.channelfromregisterajoin`install`bona.device=b.device--hive方式createtableaccount_channelROWFORMATDELIMITEDFIELDSTER 查看详情

laravel进阶任务笔记

在任务开始,我们会扩展一下新建的数据库表移植文件。刚建立的移植文件只有两列,手动添加如下:publicfunctionup(){Schema::create(‘tasks‘,function(Blueprint$table){$table->increments(‘id‘);$table->integer(‘user_id‘)->index();$table->stri... 查看详情

学习笔记js进阶语法一document对象(代码片段)

内容整理自《从0到1Javascript快速上手》下半部分-进阶语法篇 示例:write()方法和writeln()方法区别 <!DOCTYPEhtml><html> <head> <metacharset="utf-8"> <title>writeln()方法</title> <styletype="text/css... 查看详情

javascript-进阶篇

JavaScript的内置对象JavaScript中的所有事物都是对象,如:字符串、数值、数组、函数等,每个对象带有属性和方法对象的属性:反映该对象某些特定的性质的,如:字符串的长度、图像的长宽等;对象的方法:能够在对象上执行的... 查看详情

redis进阶笔记

...的成长帮助甚少。本文为大家总结了关于Redis常见用法的进阶指南,希望帮助大家加深对这门技术的理解。文章作者:何永康,腾讯CSIG后台研发工程师。大于1M的话,则每次只会扩容1M,直到达到512M。tureRedis value命令通过CRC16... 查看详情

c语言进阶笔记深入了解进阶指针(代码片段)

目录前言指针进阶字符指针指向常量字符串的指针指针数组指针数组打印数组内容数组指针对数组指针的理解&数组名和数组名数组指针的使用数组参数、指针参数一维数组传参二维数组传参一级指针传参二级指针传参函数指... 查看详情

c语言进阶笔记深入了解进阶指针(代码片段)

目录前言指针进阶字符指针指向常量字符串的指针指针数组指针数组打印数组内容数组指针对数组指针的理解&数组名和数组名数组指针的使用数组参数、指针参数一维数组传参二维数组传参一级指针传参二级指针传参函数指... 查看详情