10JavaScript函数

10函数

1.定义

通俗来讲,函数就相当于一个工厂,将某些东西放进去加工后,再吐出来加工后的产物,用来实现某个功能。函数是对象,函数名是指针。

关于如何理解函数名是指针:

<个人理解就是看内存中保存的是具体内容还是地址。>

var num = 123;
var num_a = num;
console.log(num, num_a);//123 123
//此时内存中有两个123
var obj = {nickname:'宠儿'};
var obj_a = obj;
console.log( obj, obj_a);//nickname:'宠儿' nickname:'宠儿'
//此时内存中只有一个宠儿
2.声明
function some(){
    console.log(1);//在后边加括号不报错的都是函数
}
//①函数声明:
function sum (num1 , num2){
    return num1 + num2;
}
//②用函数表达式定义函数(不推荐<--两次解析代码,影响性能,不会被提升到顶层)
var sum = function(num1 +num2){
    	//定义了变量sum并将其初始化为一个函数
    return num1 + num2;
};//注意这里的分号还要写,就像声明其他变量一样

函数声明与函数表达式的区别:

​ 解析器在向执行环境中加载数据时,会率先读取函数声明(函数提升)。并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

​ 比如以下例子,并不会影响代码的正常的运行

alert(sum(10,10));
function sum (num1,num2){
    return num1 + num2;
}

​ 而下边这个,就会报错: Uncaught TypeError: fn_b is not a function at XXX

fn_b();
var fn_b = function () {console.log( '我是函数fn_b' )};
3.调用
function some(){
    console.log('1');//此时控制台上并不会打印出1
}
some();//此时运行some函数里边的代码

function some2(abc){
    console.log(abc);
}
//函数可以无数次调用
some2 = ('露娜');
some2 = ('公孙离');
4.参数

函数的参数是按值传递的

实际参数:传递给函数的东西

形式参数:函数中的变量,可以有无数个。形参和实参是一一对应的

保存函数参数:arguments(除了箭头函数外,任何函数都有这个属性)。

  • 函数在定义时没有规定参数, 函数在执行的时候也可以往里面传入若干个参数, 参数按照顺序存储在arguments数组中
  • arguments是一个类数组对象,包含着传入函数中的所有参数,
  • arguments拥有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments 对象的函数。当函数在严格模式下运行时,访问 arguments.callee 会导致错误。
    • 严格模式:script首行或者在函数体内写"use strict"
//实参
function some1(){
	console.log('我是some1函数');
}
//形参
function factory(abc){//abc就是形参
    var abc = '皮皮虾';
    console.log(abc); 
}
//arguments
var a = 10, b = 20;
function fn(x,y){
    console.log(arguments[0]);
}
fn(a,b);
arguments应用示例:加法运算
function sum_a(){
    var result = 0,
    for( var i = 0,length = arguments.length; i < length; i++){
        if( typeof arguments[i] === 'number' ){
            result += arguments[i];
        }
    }
    console.log(result)
}
sum_a(1,1,2,3,456,645,56);
arguments应用示例:阶乘函数
function factorial(num) {
    if (num <= 1){
        return 1;
    }else{
        return num * arguments.callee(num-1);
    	//等同于-->num * factorial(num-1);(弊端:函数执行与函数名紧密耦合)
        //用到了递归
    }
}
5.分类
  • 具名函数:有名字的函数

  • 匿名函数:没有名字的函数

//具名函数:
function functionName(){
    console.log("我是个名字为functionName的函数");
}
functionName();//利用名字调用
//匿名函数:
var obj = {
	abc :function (){//此时括号前没有名字哦,即使有名字,也无法使用,没有存在的价值
        console.log('我是obj对象里的abc属性,我是一个函数');
    }    
}
obj.abc();
6.功能–>封装

以之前学过的冒泡排序为例,就是将整个冒泡排序这种算法装进一个函数里,方便以后使用。

 //将整个算法装进名为sort的函数中:
function sort (arr) {//sort[排序]
        for( var i = 0, length = arr.length; i < length - 1; i++ ){
            for( var k = 0; k < length - 1 - i; k++ ){
                if( arr[k] < arr[k+1] ){
                    var little = arr[k]
                    arr[k] = arr[k+1]
                    arr[k+1] = little
                }
            }
        }
        console.log(arr);
    };
//当想要使用时:
sort( [1,2,3,4,5] );
//也可以这样:
var arr_a = [1,4,3,5,8];
sort(arr_a);
7.return返回值
  • 一个函数内处理的结果可以使用return 返回,这样在调用函数的地方就可以用变量接收返回结果

  • 返回

  • return 关键字内任何类型的变量数据或表达式都可以进行返回,甚至什么都不返回也可以。所以有的时候return 的作用就是用来终止函数执行

  • JAVASCRIPT在事件中调用函数时用return返回值实际上是对window.event.returnvalue进行设置,而该值决定了当前操作是否继续。
    当返回的是true时,将继续操作。
    当返回是false时,将中断操作。

    而直接执行时(不用return),将不会对window.event.returnvalue进行设置,所以会默认地继续执行操作

//一个加法运算
function add(){
    var result = 0;
    for( var i = 0,length = arguments.length;i < length;i++){
        result += arguments[i];
    }
    return result;//将得到的result结果<值>返回出去
}
var some = add(1,2,3,4);
console.log( some );//10
8.作用域

📌当某个函数被调用时,会创建一个执行环境(作用域)及相应的作用域链。

📌作用域链规定我们在什么地方可以访问什么变量。作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

📌无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁(内存释放),内存中仅保存全局作用域(全局执行环境的变量对象)。

​ ❓为什么会内存释放

局部变量只在函数执行的时候使用,执行完该函数后,功能已经实现了,没有存在的必要了。假如还存在的话,其他也没有人可以再使用它,那就是浪费内存,因此js的垃圾收集器会把该局部变量当做垃圾回收掉

  • es5中只有函数作用域和全局作用域,es5只要不在函数里边去声明一个变量,这变量就是全局(window,Global)
  • 全局作用域不可以使用局部作用域里的变量,局部作用域可以访问全局里的变量和所有父作用域。
  • 对于全局和局部中名字一样的变量,采用就近原则。
  • 变量提升:使用var声明的变量会进行变量提升,提升到当前作用域的顶层
var a = 1;//全局,这里的a内存中是存在的
function functionName(){//这个花括号就是函数的作用域
    //在创建 functionName()函数时,会创建一个预先包含全局变量对象的作用域链
    var a = 2;//局部,这里的a只在函数执行的过程中存在
}
9.闭包
(1)可以这样理解闭包:
  1. 闭包是指有权访问另一个函数作用域中的变量的函数。
  2. 闭包让函数在执行完成之后不释放内存;
  3. 函数执行完成之后阻止作用域被销毁。
(2)闭包形成的方式:
  1. 函数a返回(return)函数b,函数b在函数a之外执行,并且会用函数a里边的变量
  2. 在全局中声明一个变量保存这个声明的函数。(必须有人知道这个函数)
(3)例子:
//🎈第一个:返回函数并声明变量保存
function a() {
        var num = 123;
        return function b() {//函数a返回了函数b
            console.log( num );//在函数b里边使用了函数a里的num变量
        } 
    };
var result = a();//声明一个result去保存a()
result();//调用result()后,控制台可打印出123
//如果直接使用a(),控制台并不会打印出num的值
	//📝也可以这样写:
function a() {
    var num = 123;
    function b() {
        return num;
    }
    return b();//返回的是这个函数结果
    //return b;//返回的是个函数
}
var result = a();
cobsole.log(result);//将返回的函数值123打印出来
//result();//调用的前提是上边返回的是个函数
//🎈第二个:返回的不是函数时
function c() {
        var str = 'hello world';
        return {
            some : str
        };
    };
var result_b = c();
//result_b();//报错:Uncaught TypeError: result_b is not a function
console.log( result_b.some );//hello world
//🎈第三个:形成短暂闭包情况
function d() {
    var arr = [1,2,3];
    return function e(){
        console.log(arr);
    }
}
var res = d();
d()();//此时会短暂的形成闭包(两次调用)
(4)闭包与变量

闭包只能取得包含函数中任何变量的最后一个值。

闭包所保存的是整个变量对象,而不是某个特殊的变量

function createFunctions(){
    var result = new Array();
    for (var i = 0,i < 10; i++){
        result[i] = function(){
            return i;
        }
    }
    return result;
}

​ 这个函数会返回一个函数数组。表面上看,似乎每个函数都应该返回自己的索引值,即位置 0 的函数返回 0,位置 1 的函数返回 1,以此类推。但实际上,每个函数都返回 10。因为每个函数的作用域链中都保存着 createFunctions() 函数的活动对象,所以它们引用的都是同一个变量 i 。 当createFunctions()函数返回后,变量 i 的值是 10,此时每个函数都引用着保存变量i的同一个变量对象,所以在每个函数内部i的值都是 10。

​ 这时,我们可以利用立即执行函数来解决这个问题

function createFunctions(){ 
 	var result = new Array(); 
 	for (var i=0; i < 10; i++){ 
		 result[i] = function(num){ 
 			return function(){ 
 				return num; 
 			}; 
 		}(i);
 	} 
 	return result; 
}

​ 在这个版本中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。这里的匿名函数有一个参数 num,也就是最终的函数要返回的值。在调用每个匿名函数时,我们传入了变量 i。由于函数参数是按值传递的,所以就会将变量 i 的当前值复制给参数 num。而在这个匿名函数内部,又创建并返回了一个访问 num 的闭包。这样一来,result 数组中的每个函数都有自己num 变量的一个副本,因此就可以返回各自不同的数值了。

**练习:**点击盒子,控制台打印出保存该盒子的数组下标

<body>
    <div class="box">0</div>
    <div class="box">1</div>
    <div class="box">2</div>
    <div class="box">3</div>
</body>

<script>
var boxs = document.querySelectorAll('.box');
    for( var i = 0; i < boxs.length; i++ ){
        (function (a){
            boxs[a].onclick = function(){
                console.log(a);
            };
        })(i);
    };
</script>
(5)闭包面试题
function fun(n, o){
        console.log(o);
        return {
            fn: function (m) {
                return fun(m, n);
            }
        };
    };

看以上代码,执行完下列代码的每一行后,判断控制台打印的o的值

 //第一题:
var a = fun(1);
 a.fn(2);
 a.fn(3);
 a.fn(4);
//第二题:
fun(1).fn(2).fn(3).fn(4);

答案是:

 //第一题:
var a = fun(1);
 a.fn(2);//undefined 1
 a.fn(3);//undefined 1
 a.fn(4);//undefined 1
/*理由:
	声明的变量a保存fun()时,形成闭包;
	a.fn(2);短暂的形成闭包,执行完成后,该执行环境销毁
	a.fn(3)和a.fn(4)同理
*/
//第二题:
fun(1).fn(2).fn(3).fn(4);//undefined 1 2 3
//理由:这个时候闭包一直都存在,之前保存的变量值未被销毁
10.this
(1)指向

this的默认指向window,还可以是调用函数的主体对象;(看谁调用了他)

  • 在全局执行环境中使用this,指向window
console.log(this); //Window
console.log(typeof this); //object
console.log(this === window); //true
  • 在函数中使用this,看是谁调用了他
function fn_a() {
        function some() {
            console.log(this);//这里的this依然是window
        };
        some();
    }
    fn_a();

​ 在对象方法中绑定:

var obj = { //声明一个obj对象来保存下边的值
        nickname: '小明',
        sayName: function () {
            console.log(this);//打印出整个obj对象里的内容 
            console.log('hello 我是:', this.nickname);//hello 我是: 小明
        }
    };
    obj.sayName();// obj.sayName调用了上边这个函数
  • 严格模式下单独使用this,指向window
"use strict";
var x = this;
  • 严格模式下,函数中使用this,this 是未定义的(undefined)。
"use strict";
function myFunction() {
  return this;
}

实例1:(主要为理解this指向)

var nickname = '小刚';
    var a = {
        nickname: '小烈',
        b: {
            nickname: '小红',
            c: function() {
                console.log('hello 我是:', this.nickname);
            }
        }
    };
    a.b.c();
//结果打印出  hello 我是: 小红

实例2:(主要使用this节省内存)

function some() {
        console.log(this);
        console.log( this.nickname );
    };
some();
var aLI = {
    nickname: '公孙离',
    introduce: some
};
aLi.introduce();
var LiBai ={
    nickname: '李白';
    introduce: some
};
LiBai.introduce();
(2)切换this绑定的对象

由于jsthis的指向受函数运行环境的影响,经常发生改变,有时会使开发变得困难,因此为避免出现一些不必要的问题,出现以下三种方法:

call ()

​ 🎈改变this指向:call第一个实参是this指向

var obj = {};
var f = function(){
	return this;
};
console.log(f() === window);  // this 指向window
console.log(f.call(obj) === obj)  //改变this 指向 obj

​ 🎈用call可以去绑定任意的数据类型,如果是undefinednull则等于绑定了window。

function foo(a) {
        console.log(a);//我是实参
        console.log(this);//{nickname: '小明'}
    };
foo.call( {nickname: '小明'}, '我是实参' );
 var obj_b = {
        b: {
            c: function () {
                console.log(this)
            }
        }
    }
obj_b.b.c.call(undefined);

​ 🎈call可以接受多个参数,第一个参数是this指向的对象,之后的是函数回调的所需的入参。

function add(a, b) {
  return a + b;
}

add.call(this, 1, 2) // 3

​ 🎈所传的参数一次性使用,立即执行,不会产生新内容(不会改变原函数)

function foo(a) {
        console.log(a);//我是实参
        console.log(this);//{nickname: '小明'}
    };
foo.call( {nickname: '小明'}, '我是实参' );
foo();//undefined window

​ 📝应用:调用对象的原生方法

var obj = {};
obj.hasOwnProperty('toString') // false

// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
  return true;
};
obj.hasOwnProperty('toString') // true

Object.prototype.hasOwnProperty.call(obj, 'toString') // false
apply()

​ 🎈改变this指向:apply第一个实参是this指向

function some_a(a, b){
        console.log(this); //{ nickname: '小明' }
    };
some_a.apply( { nickname: '小明' }, [ '123', '456' ] );

​ 🎈只接受两个参数,第一个是this指向,第二个必须是数组类型,数组每一项一一对应函数形参

function some_a(a, b){//a对应数组的第0项,b对应数组的第1项
        console.log(a, b);//123 456
    };
some_a.apply( { nickname: '小明' }, [ '123', '456' ] );

​ 🎈所传的参数一次性使用,立即执行,不会产生新内容(不会改变原函数)

function some_a(a, b){//a对应数组的第0项,b对应数组的第1项
        console.log(a, b);//123 456
    };
some_a.apply( { nickname: '小明' }, [ '123', '456' ] );
some_a();//undefined undefined

​ 📝应用:遍历数组

 //一个加法运算
var arr = [1,2,3];
    function add(a, b,c){
        console.log( a + b + c );//6
    };
 add.apply( null, arr );
//输出数组的最大值:
var a = [24,30,2,33,1]
Math.max.apply(null,a)  //33
bind()

bind 用于将函数体内的this绑定到某个对象,然后返回一个新函数。并不会立即执行

var obj = {
        nickname: '小明'
    };
function abs(a,b) {
        console.log(this);//{nickname: '小明'}
        console.log(a,b)//1 2
    };
var result = abs.bind(obj);
result(1,2);

**实例:**点击每个div,将每个div中的字体变成不同的颜色

<style>
        *{
            margin: 0;
            padding: 0;
            list-style: none;
        }
        .box{
            width: 100px;
            height: 100px;
            background-color: orange;
        }
        .box + .box{
            margin-top: 10px;
        }
</style>
<body>
    <div class="box">0</div>
    <div class="box">1</div>
    <div class="box">2</div>
    <div class="box">3</div>
</body>
<script>
var boxs = document.querySelectorAll('.box');
function a(e, color) {//字体颜色
     // console.log(color)
     console.log( this );
     this.style.color = color;
    };
//给div中字体赋予不同的颜色:
var colorful = ['#58a', 'purple', 'hotpink', 'yellowgreen'];
for( var i = 0; i < colorful.length; i++ ){
      boxs[i].onclick = a.bind( boxs[i], 123, colorful[i] );
    };  
</script>

小结:

  • callapply:相同点都是临时借用一个函数,并替换this为指定对象,不同点在于传参方式不一样,并且都是立即执行;
  • bind:基于现有函数,创建一个新函数,并且永久绑定this为其指定对象,可绑定参数,不过只是创建函数,不立刻执行。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值