JavaScript精粹(三)- 函数&作用域

  • 函数(Function)是代码的可重用块,它们封装了特定的功能或行为,并可以在需要时通过调用其名称来执行。函数可以接受输入(参数),执行一系列操作,并可能返回输出(返回值)。函数是JavaScript编程中的基本构建块,它们使得代码更加模块化、可维护和可重用。

  • 作用域(Scope)是JavaScript中定义变量、函数和对象可访问性的区域。简单来说,它决定了哪些变量、函数和对象在特定的代码块中可用。JavaScript有两种主要的作用域:全局作用域和局部作用域。全局作用域中的变量和函数可以在整个脚本中访问,而局部作用域(例如函数内部或代码块内部)中的变量和函数则只能在那个特定的作用域中访问。

  • 函数通常有自己的局部作用域,这意味着在函数内部声明的变量不会与全局作用域中的变量冲突。这种封装性有助于防止命名冲突,并使代码更加清晰和可维护。此外,JavaScript还引入了闭包(Closure)的概念,它允许函数记住并访问其自己的词法环境(lexical environment),即使该函数在其外部作用域执行完毕后仍然可以访问这些变量。

总之,函数是JavaScript中组织和复用代码的基本单元,而作用域则定义了这些代码单元中变量和函数的可见性和生命周期。通过合理使用函数和作用域,我们可以编写出更加健壮、可维护和可读的JavaScript代码。

十、函数

1.1函数的作用

函数的作用:在出现大量程序相同的时候,可以封装为一个function,这样只需要调用一次就能执行很多语句,模块化编程,让复杂的逻辑变得简单。

1.2 函数的定义

通俗的说,函数就好像我们css的类名一样,我们书写类里面的样式的时候,就是函数的定义,给标签加上类名,可以多次使用它,这就叫做函数调用。

            //定义函数
function fn(){
    alert("Daniel好帅,就像老鼠爱大米!")
}
//函数调用 在定义函数之前或者之后都可以,我们后面讲
fn();

这个时候,我们的函数有名字了,那么,我们可以把它放到我们的事件后面

oBox.onclick = fn;
//定义函数
function fn(){
    alert("Daniel好帅,就像老鼠爱大米!")
}

这个时候就是这么回事,事件触发的时候,我们后面的函数就会执行,但是要注意的是,事件后面的函数不要加括号,那样子他就自执行了,至于执行以后是什么东西。我们后面再讲。

1.3 匿名函数

上面我们讲的定义函数和调用函数,那是有名函数,只有有名函数才可以调用,那么,在我们最开始的时候我们事件后面那一长串的函数又是什么呢?就是我们的匿名函数

oBox.onclick = function () {
                alert("Daniel好帅,就像老鼠爱大米!");
};

我们可以看到,在function后面没有函数名,像这样的函数就叫做匿名函数,不能像有名函数一样单独存在,单独存在的匿名函数是没有任何意义的。只有事件触发的时候我们的匿名函数才会执行。

1.4 函数表达式

什么样的函数能加()执行?
有名函数的函数名() 函数表达式()
有没有办法将匿名函数变为函数表达式?

将匿名函数变为函数表达式 并加括号执行的几种办法:
最重要:

(function () {
        console.log("Daniel一号");
    })();
 
var a = function () {
     console.log("Daniel二号");
    }();
 
+function () {
        console.log("Daniel三号");
    }();
 
-function () {
        console.log("Daniel四号");
    }();
 
!function () {
        console.log("Daniel五号");
    }();
 
~function () {
        console.log("Daniel六号");
    }();
 
(function () {
        console.log("Daniel七号");
    }());

1.5 函数参数

在函数里面是可以传变量的

1.5.1 参数定义

写在函数块里面的叫做形参(一定是变量),接收实参 如: erGou(a,b)等价于erGou(var a, var b),因此不需要单独的在定义a,b
写在函数执行括号里面的叫做实参(可能是直接量也可能是变量),给形参传值

function erGou(a, b) {
        console.log(a + b);
    }
erGou(10, 20); //30 函数执行的时候将括号里面的实参对应赋值给函数里面的形参等价于a=10,b=20
erGou(5, 10);     //15

1.3.2 应用

function daGou(a) {
        alert(a);
        a();
    }
daGou( function () {console.log("哈哈");} );

以上语句相当于:

var a = function () {console.log("哈哈");}
alert(a);
a();

现在函数赋值给a了,a就是一个函数表达式,那就直接a加括号执行就行了
大家记住,函数执行只有两种情况,一是事件触发,二是函数名或函数表达式加括号执行

1.6 参数对应问题

function daGou(a,b) {
            console.log(a+b);
        }

1.6.1正常情况下

daGou(1,2); //3

1.6.2 形参个数比实参个数少

不影响

daGou(1,2,3); //3

1.6.3 形参个数比实参个数多

daGou(1); //NaN

这个时候a = 1 ,b因为没有赋值,所以是undefined
那么,1 + undefined = NaN

1.6.4 arguments 不定参

我们先来做一个小案例求和

function sum(x,y,z) {
            console.log(x+y+z);
        }
        sum(1,2,3);

如果说有十个,一百个求和呢?
当我们的实参和形参没有办法一一对应的时候
我们每一个函数都有一个arguments不定参(类数组),里面存放了我们所有的实参

function sum1() {
            var n = 0;
            for( var i=0; i<arguments.length; i++ ){
                n += arguments[i];
            }
            console.log(n);
        }
        sum1(1,2,3,4,5,6,7,8,9);

这一个时候我们就把所有的用户传入的参数都给拿到并且求和了

1.7 return函数返回值

现在我们来考虑一个事情,当我们的函数执行完毕以后是什么东西?

function daGou() {
            console.log("Daniel");
        }
        var a = daGou();
        console.log(a);  //undefined

由上面的例子我们可以看出来
首先 函数执行输出“Daniel” 然后将函数执行完以后的数据赋值给a我们再输出a
这个时候会输出undefined 也就证明函数执行完毕以后返回undefined
那我们这里就有一个return 可以改变函数的返回值,return什么就返回什么

function daGou(a,b) {
            console.log( a + b );
            return a+b;
  console.log("哈哈");
        }
        var a = daGou(2,3);
        console.log(a);

且函数遇到return就不再执行之后的语句

在之前我就给大家讲过这样的一个例子

function daGou() {
            console.log("Daniel");
        }
        document.onclick = daGou();

这时候我们发现我们的点击事件失效,那这是为什么呢,就是因为我们刷新页面以后我们的daGou直接执行,执行完以后返回undefined 将一个undefined给点击事件很显然是不行的

那我们要怎样才可以实现呢?

function daGou() {
            function erGou() {
                console.log("Daniel");
            }
            return erGou;
        }
        document.onclick = daGou();

1.8 递归

先来看看用js实现阶乘

function factorial(x) {
        var  product = 1;
        for( var i=1; i<=x; i++ ){
            product *= i;
        }
        return product;
    }
    console.log(factorial(3));

有没有更简单一点的方法呢?
递归:在函数里面调用自己本身

    function factorial(x) {
        return x === 1 ?  1 :  x*factorial(x-1);
    }
    var product = factorial(5);
  console.log(product);

上面的语句的含义是:

/**
 * product =5*factorial(5-1)
 * product =5*4*factorial(4-1)
 * product =5*4*3*factorial(3-1)
 * product =5*4*3*2*factorial(2-1)
 * product =5*4*3*2*1
 */

十一、作用域

作用域:将js代码的解析分为很多个域,一个域解析完了再解析另一个域
script就是最大的一个域 函数执行也是一个域

1.1 Javascript的解析顺序

可以弹出’Daniel’

<script>
    var a = 'Daniel';
</script>
<script>
    alert(a);
</script>

报错

<script>
    alert(a);
</script>
<script>
    var a = 'Daniel';
</script>

弹出undefined 但不报错

<script>
    alert(a);
    var a = 'Daniel';
</script>
  • 总结:两个不同的script标签先执行完前一个的全部代码 前一个定义了的变量后面的就可以使用

在同一个script标签里面 按从上往下的原理来说应该是报错,但是并没有,说明在同一个标签里面只用从上往下来描述执行顺序是不合理的

1.2 作用域里面的执行顺序

1.2.1 作用域里面的执行顺序:

  1. 定义
    var a(定义变量名 不会找赋值) 函数定义(有名函数的定义)
  2. 执行(除了定义都是执行 从上往下执行)
    我们再来看看刚才的例子
alert(a);
var a = 'Daniel';
alert(a)
//1. 定义:
  var a
//2. 执行:
  alert(a)     //a已经var了   因此有a这个变量了但是没有赋值    所以弹出undefined
  a = 'Daniel';   //弹窗执行以后才给a赋值
  alert(a)     //这个时候a已经赋值了因此就可以弹窗了

1.2.2 为什么函数可以在定义前面执行

    daGou();
    function daGou() {
        console.log('Daniel');
    }

分析:

//1. 定义:
  function daGou() {
      console.log('Daniel');
  }
//2. 执行:
  daGou();

1.3新的作用域和作用域链

每遇到一个新的作用域就会开始新的定义和执行步骤 在新的作用域执行完毕以后又会回到上级作用域继续执行
作用域链:在执行时遇到变量会先在当前作用域寻找 如果没有找到则会向上一级作用域找 如果一直没有找到就是报错(注意:undefined是找到了,但是还没有赋值) 但是 作用域链只能子作用域往父作用域找 不能父作用域往子作用域找

script是一个大的作用域

我们来看看下面的一个例子:

    var dagou = '大狗';
    function daGou() {
        console.log(dagou);
        var dagou = '二狗';
        console.log(dagou);
    }
    daGou();

1.定义:

    var dagou
    function daGou() {
        console.log(dagou);
        var dagou = '二狗';
    }

2.执行:

 dagou = '大狗';

 daGou();    ==>新的作用域
     1.定义:
     var dagou
    2. 执行:
     console.log(dagou); //undefined
     dagou = '二狗';
    console.log(dagou); //二狗

1.4 关于return

fn()();
var a = 0;
function fn(){
	alert( a );
	var a = 3;
	function c(){
		alert( a );
	};
	return c;
};
var a = 5;
function fn(){
	var a = 10;
	alert( a );
	function b(){
		a ++;
		alert( a );
	};
	return b;
};

var c = fn();
c();
fn()();
c();

要注意的是,在定义变量的时候如果不写var 就是定义的一个全局变量

1.5全局变量的作用

全局变量挺有用的,有两个功能:

1.5.1 功能1:通信,共同操作同一个变量

两个函数同时操作同一个变量,一个增加,一个减少,函数和函数通信。

var num = 0;
function add(){
num++;
}
function remove(){
num--;
}

1.5.2 功能2:累加,重复调用函数的时候,不会重置

var num = 0;
function baoshu(){
num++;
console.log(num);
}
 
baoshu(); //1
baoshu(); //2
baoshu(); //3

如果num定义在baoshu里面,每次执行函数就会把num重置为0:

function baoshu(){
var num = 0;
num++;
console.log(num);
}
 
baoshu(); //1
baoshu(); //1
baoshu(); //1

十二、闭包

1.1 概念

任何的培训机构,任何的书,讲闭包,一定是下面的案例:

function outer() {
        var a = 3;
        function inner() {
            console.log(a);
        }
        return inner;
    }
    var inn= outer();
    inn();

推导过程:
我们之前已经学习过,inner()这个函数不能在outer外面调用,因为outer外面没有inner的定义

function outer(){
    var a = 3;
    function inner(){
        console.log(a);
    }
}
//在全局调用inner但是全局没有inner的定义,所以报错
inner();

但是我们现在就想在全局作用域下,运行outer内部的inner,此时我们必须想一些奇奇怪怪的方法。
有一个简单可行的办法,就是让outer自己return掉inner:
这就说明了,inner函数能够持久保存自己定义时的所处环境,并且即使自己在其他的环境被调用的时候,依然可以访问自己定义时所处环境的值

一个函数可以把它自己内部的语句,和自己声明时所处的作用域一起封装成了一个密闭环境,我们称为“闭包” (Closures)。

  • 闭包 = 自己的语句+ 自己声明时的环境的作用域

1.2 闭包的性质

每次重新引用函数的时候,闭包是全新的,无论它在何处被调用,他总是能访问它定义时所处的作用域中的全部变量(这里说的全新是指赋值给新的函数的时候是全新的,并不是调用相同的函数是全新的)

    function outer() {
        var a = 3;
        function inner() {
            a++;
            console.log(a);
        }
        return inner;
    }
    var daGou = outer();
    var erGou = outer();
    daGou(); //4
    daGou(); //5
    daGou(); //6
    erGou(); //4

最常用的闭包

var aLi = document.querySelectorAll('li');
    for( var i=0; i<5; i++ ){
        (function (m) {
            console.log(m);
            aLi[m].onclick = function () {
                alert(m);
            }
        })(i)
    }

我们将i传入function,这时候点击事件就是一个闭包,记住了定义时所处的环境的变量,也就是每次都记住了m的值

十三、数组字符串相关操作

1.1 数组

1.1.1 数组的初步认识

  • 数组里面的各项可以是任意的数据类型
  • 且数组可读可写(可以得到值也可以修改值) 没有的项就是undefined
    数组各项可以是任意类型
var arr = [function () {},'阳阳',13,null]; //数组各项可以是任意类型

数组可读可写 没有的项就是undefined

 arr.length = 8;
    console.log(arr.length);     //获取数组长度
    console.log(arr[8]);         //undefined
    arr[8] = '哈哈';
    console.log(arr[8]);         //哈哈

1.1.2 数组的遍历

数组里面存放的是一组数,那么我们经常需要对这些数字都进行一些操作。
就需要用for循环语句来遍历它。比如我们将一个数组里面的值赋值给另外一个数组

var arr = [a,b,c,d];
var arr2 = [];
for(var i = 0 ; i <= arr.length - 1 ; i++){
   arr2[i] = arr[i];
}

1.1.3 数组的一些方法属性

var arr = [function () {},'阳阳',13,null];
  1. length 获取数组的长度
console.log(arr.length); //4
  1. push() 在数组末尾添加项目,可添加一个,也可以添加多个。
    var arr = [function () {},'阳阳',13,null];
    arr.push('Daniel','阳阳');
    console.log(arr);
  1. pop() 删除数组最后一项,能够返回被删除的那一项
 var arr = [function () {},'阳阳',13,null];
 var a = arr.pop();
   console.log(arr);    //[function () {},'阳阳',13]
   console.log(a);      //null
  1. unshift() 在数组的第0项两家项目,可添加一个,也可添加多个
var arr = [function () {},'阳阳',13,null];
arr.unshift('Daniel','阳帅');
console.log(arr);
  1. shift() 删除数组第0项,可以返回被删除的那一项
    var a = arr.shift();
    console.log(a); //function(){}
    console.log(arr); //["阳阳", 13, null]
  1. 旋转木马轮播图的原理
arr.unshift(arr.pop())   //删除最后一项,添加到第一项
arr.push(arr.shift())   //删除第一项,添加到最后一项
  1. concat() 合并两个数组 返回一个新的数组
arr1.concat(arr2);    //注意,合并以后是形成一个新的数组,并没有改变原来的数组
  1. slice() 截取数组
arr.slice(start,end)  返回一个新数组,包括start不包括end,取出了end-start项
var arr1 = [function () {},'阳阳',13,null];
var a = arr1.slice(1,3);
console.log(a); //'阳阳',13
只有开头:arr(start) 从start开始截取后面所有
  1. splice() 多功能插入,删除,替换(数组被改变)不能用于字符串
    一个参数的时候是从多少项开始删除后面所有
    var arr1 = [function () {},'阳阳',13,null];
    arr1.splice(-2);     //从倒数第二项开始删除后面所有
    console.log(arr1);

    arr1.splice(1);     //从第一项开始删除后面所有
    console.log(arr1);

两个参数的时候 第一个参数表示从多少项开始删除 第二个参数表示删除多少项

var arr1 = [function () {},'阳阳',13,null];
arr1.splice(1,1);
console.log(arr1);   //[ƒ, 13, null]

三个参数的时候 第三个参数为之前删除的项替换的项

var arr1 = [function () {},'阳阳',13,null];
arr1.splice(1,1,'喜洋洋');
console.log(arr1);   //[ƒ,'喜洋洋', 13, null]
  1. reverse() 逆序,立即让数组倒置(数组被改变)
var arr1 = [function () {},'阳阳',13,null];
arr1.reverse();
console.log(arr1); //[null, 13, "阳阳", ƒ]
  1. sort() 数组排序,默认是按照字符编码排序
    sort里面有个参数,这个参数是一个函数
  • 如果正序,a>b,return 1; 如果倒序,a>b; return -1;
   var arr2 = [3,4,2,5,1,1];
    arr2.sort(function (a,b) {
        if(a<b){
            return -1;
        }else if(a>b){
            return 1;
        }else{
            return 0;
        }
    });
    console.log(arr2);
  1. join将数组转为字符串 参数为需要连接的字符
var arr1 = [function () {},'阳阳',13,null];
var a = arr1.join('-');
console.log(a); //function () {}-阳阳-13-
console.log(typeof a); //string

1.2 字符串的属性方法

1.2.1 charAt()

返回在指定位置的字符

	var str = '我爱Daniel!';
    console.log(str.length); //注意:一个空格也算是一个字符串
    console.log(str[1]);      //兼容ie8以上
    console.log(str.charAt(2));      //兼容所有浏览器

虽然用下标也可以,但是能兼容全部浏览器更好,因此我们会经常使用charAt()

1.2.2 concat()

字符串连接

var str1 = '我爱Daniel!';
var str2 = '就像老鼠爱大米!';
console.log(str1.concat(str2));   //字符串拼接 拼接以后str1在前str2在后

1.2.3 indexOf()

检索字符串 检索某个字符串首次出现的位置 如果没有检测到则返回-1

var str1 = '我爱Daniel!';
console.log(str1.indexOf('爱')); //1

两个参数的时候 第一个参数表示要检测的字符 第二个参数表示从多少的下标开始检测

var str3 = 'Daniel很帅,Daniel,我爱Daniel!';
console.log(str3.indexOf('Daniel',2)); //如果检测到则返回第一个被检测的字符串的序列

现在我们来做一个小案例:输出str3里面所有’Daniel’字符串的位置

for( var i=0; i<str3.length; i = a +1 ){ //这里要注意的是我们下一次加的长度要加上之前检索到的字符串的位置避免重复检索
        var a = str3.indexOf('Daniel',i); //将返回的字符串位置赋值给a
        if(a === -1){
            break;   //如果a===-1说明没有找到字符串,那么直接结束循环
        }else{
            console.log(a); //如果a不等于-1说明检索到字符串的位置,那么直接输出
        }
    }

1.2.4 replace()

替换 将第一个目标字符替换为第二个参数的字符

var str4 = "abcda";
var x = str4.replace("a","0");   //将第一个a替换成0
console.log(x); // 0abcda

1.2.5 split()

把字符串转为数组,从什么地方拆,就是参数,拆开的是字符串 如果想每一个字符串都拆开,那就传空字符串

var str4 = '大锤 二锤';
var a = str4.split(' ');
console.log(a); //["大锤", "二锤"]

1.2.6 substr()

截取子串
第一个参数是起始位置,第二个参数是要截取的字符串的长度

var str6 = 'abcde';
str6.substr(1,3) //bcd  从下标为1的开始,截取3个长度的字符

1.2.7 slice()

截取字符串
当起始位置为负数时,slice会按照倒序来数(但是要注意参数一定要是左边数到右边) 当为正数时,slice只能接受第一个数比第二个数小的数 取不到最后一个数

var str = "我爱Daniel!";
var s = str.slice(-5,4);
console.log(s); //Daniel

1.2.8 substring()

截取字符串
当起始位置为负数时,substring会将负数转为0
substring会将两者较小的作为起始位置
取不到最后一个数

    var str7 = 'Daniel好帅!';
    var a = str7.slice(4,7);
    var b = str7.slice(4,-1);
    var c = str7.substring(4,7);
    var d = str7.substring(-1,7);
    console.log(a);   //好帅
    console.log(b);  //好帅
    console.log(c);  //好帅
    console.log(d);  //Daniel好帅

十四、for in

1.1 点和[]操作

在js中,只有对象点属性或者方法,变量是不可以被点的 并且,可以用点操作的几乎都可以用[]代替 在替代的时候[]里面不是变量就要加引号

document.querySelector('div').style.cssText = 
                    'height: 100px; width: 100px; background: deeppink;'

等价于

document['querySelector']('div')['style']['cssText'] = 
                    'height: 100px; width: 100px; background: deeppink;'

[]操作有什么好处呢: 可以为变量

var a = 'name';
console.log(shuaiGe.a);  //没有a这个属性
console.log(shuaiGe[a]);    //变量a

1.2 for in

JSON里面我们想要拿到所有的属性和值不可能一个一个去点 并且我们怎么样我们只知道名字的情况下得到里面所有的属性和值呢? 因此我们需要像for循环一样的遍历语法 因此for in就应运而生了
其中k代表属性(和for循环一样k可以是任意变量) shuaiGe代表你要遍历的对象

var shuaiGe = {
        'name' : 'Daniel',
        'age' : '18',
        'sex' : '男',
    };
 
for (var k in shuaiGe) {
        console.log(k);     //获取到所有JSON的属性
        console.log(shuaiGe.k);  //k是变量因此我们不能直接点
        console.log(shuaiGe[k]);  //获取到所有的JSON的值
}

除了遍历JSON我们还可以遍历对象 比如window下面的,因为方法下面会有很多的属性或者方法

for (var k in window) {
        console.log(k);
}

我们直接使用window下面的方法的时候可以省略window的 比如:alert() 其实是window.alert() document window.document
数组也是对象 我们来用for in遍历数组 我们可以发现数组的属性是下标

var arr = ['大狗','二狗'];
    for (var k in arr) {
        console.log(k);
}

十五、获取实际样样式

oDiv.style.cssText = 'height: 100px; width: 100px; background: deeppink;';  //js都是操作的行内样式
console.log(oDiv.style.height); //通过点style获取的是行内样式

如果我们想获取当前生效的样式

var a = getComputedStyle(oDiv).width;   //兼容IE9及以上
var b = oDiv.currentStyle.width;     //兼容IE8及以下
console.log(a);
console.log(b);

所以我们需要写一个方法封装起来

function getStyle(obj,property) {
  return obj.currentStyle ? obj.currentStyle[property] : getComputedStyle(obj)[property];
}

【上一篇】JavaScript精粹(二)- 属性&循环
【下一篇】JavaScript精粹(四)- 定时器&运动框架

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

经理,天台风好大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值