JS——函数进阶

主要内容:

  • 函数的多种定义和调用方式
  • 说出this的指向,改变函数内部this的指向
  • 严格模式的特点
  • 把函数作为参数和返回值传递
  • 闭包的作用
  • 递归的两个条件
  • 深拷贝和浅拷贝的区别

一、函数的定义和调用

1.1 函数的定义方式

1. 函数声明方式function关键字(命名函数)

function fn() {}

2. 函数表达式(匿名函数)

var fun = function() {}

3. 利用 new Function('参数1', '参数2', '函数体');

var f = new Function();

var m = new Function('a','b','console.log('123')') //里面的参数和函数体必须用引号引起来
  • 第三种方式执行效率低,不方便属性,较少使用
  • 所有的函数都是 Function 的实例(对象)
  • 函数也属于对象


1.2 函数的调用方式

(1)普通函数

function fn(){
    console.log('人生的巅峰');
}
fn();
fn.call();

(2)对象的方法

var o = {
    sayHi: function(){
        console.log('人生得巅峰')
    }
}
o.sayHi();

(3)构造函数

function Star() {}
new Star();

(4)绑定事件函数

btn.onclick = function() {};// 点击了按钮就可以调用这个函数

(5)定时器函数

setInterval(function(){},1000); // 这个函数是定时器,自动一秒钟调用一次

(6)立即执行函数

(function(){
    console.log('人生的巅峰')
})();
// 立即执行函数是自动调用

1.3 函数内this指向的问题

函数的不同调用方式决定了this的指向不同,一般情况下,this指向函数的调用者

1. 普通函数     this 指向window

function fn() {
    console.log('普通函数的this' + this );
}
window.fn();

2. 对象的方法     this 指向的是对象 o(函数的调用者)

var o = {
    sayHi: function() {
        console.log('对象方法的this:' + this );
    }
}

3. 构造函数     this 指向 ldh这个实例对象,原型对象里面的this指向的也是 ldh这个实例对象

function Star() {};
Star.prototype.sing = function(){}
var ldh = new Star();

4. 绑定事件函数     this指向的是函数的调用者 btn这个按钮对象

var btn = document.querySelector('button');
btn.onclick = function(){
    console.log('绑定事件函数的this:' + this);
};

5. 定时器函数     this 指向的 window

setInterval(function(){
    console.log('定时器的this:' + this);
},1000);

6. 立即执行函数  this 指向的是 window

(function(){
    console.log('立即执行函数的this' + this )
})();


二、this

2.1 改变函数内部 this 指向

JavaScript为我们专门提供了一些函数方法来帮我们更优雅的处理函数内部this的指向问题,常用的有bind()、call()、apply()三种方法

(1) call

  • 调用函数
  • 改变函数内this的指向
  • 它的参数是 arg1,arg2,从事实现参数的运算
  • call主要作用可以实现继承
var o = {
    name: 'andy'
}
function fn(a,b) {
    console.log(this);
console.log(a+b); }; fn.call();
// 调用函数,此时的结果是 window fn.call(o,1,2); // 此时的结果是 对象o,改变了this的指向,把fn函数内部的this指向了对象o

上面图片中的 Father.call(this, uname, age, age, sex) 这行代码首先调用了Father这个函数,然后把Father内部的this的指向修改为了指向Son,这样子构造函数就可以继承父构造函数的属性了,相当Son添加了Father里面的三个属性

(2)apply

  • 调用函数
  • 改变函数内this的指向
  • 它的参数必须是数组(伪数组也可以)
  • apply的主要应用 比如可以利用apply借助于数学内置对象求最大值
fun.apply(thisArg, [argsArray])

thisArg:  在fun函数运行时指定的this值
argsArray: 传递的值,必需包含在数组里面
返回值就是函数的返回值,因为他就是调用函数
var o = {
    name: 'andy'
}
function fn(arr) {
    console.log(this);
console.log(arr); // 打印出来的是字符串'pink' }; fn.apply();
// 调用函数,此时的结果是 window fn.apply(o); // 此时的结果是 对象o,改变了this的指向,把fn函数内部的this指向了对象o
fn.apply(o,['pink']) // 参数必须是数组的形式
var arr = [1,66,3,99,4]; // 借助于数学内置对象求最大值
var max = Math.max.apply(null,arr);//第一个参数本来是要写改变this指向的,但是这里不需要改变this指向,写为了null,得到的max就是数组里面的最大值

//这里写null有点不太合适,可以把它改为函数的调用者,这里的调用者就是Math,因此可以改为Math
var max = Math.max.apply(Math,arr);

(3)bind

  • bind()方法不会调用函数,
  • 可以改变函数内部的this指向
  • 返回的是原函数改变this之后产生的新函数
  • 如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向此时用bind
fun.bind(thisArg, arg1, arg2,...)
thisArg : 在fun函数运行时指定的this值 arg1, arg2:传递的其他参数 返回由指定的this值和初始化参数改造的原函数拷贝
var o = {
    name: 'andy'
};
function fn(a,b) {
    console.log(this);
console.log(a + b); }; var f = fn.bind(o, 1, 2);//不会调用原来的函数,可以改变this指向,返回的是原函数改变this指向之后产生的新函数
f();//输出{name:'andy'},3
我们有一个按钮,点击之后就禁用这个按钮,3秒钟之后开启这个按钮
<button>点击</button>
方法一: <script> var btn = document.querySelector('button') btn.onclick = function() { this.disabled = true; //这里的this指向的是 btn 这个按钮
var that = this; setTimeout(function(){ that.disabled = false; //定时器函数里面的this指向的是window },3000) } </script>

方法二:
<script>
var btn = document.querySelector('button')
btn.onclick = function() {
    this.disabled = true; //这里的this指向的是 btn 这个按钮
setTimeout(function(){ this.disabled = false; //定时器函数里面的this指向的是window }.bind(btn),3000) //在定时器的 外面 绑定一个bind(),从而改变定时器函数里面的this指向 } </script>

bind(btn)还可以写成 bind(this),这里面的 this 就是指的 btn,因为bind在定时器函数的外面,在onclick函数的里面

方法三:
<script>
var btn = document.querySelector('button')
btn.onclick = function() {
    this.disabled = true; //这里的this指向的是 btn 这个按钮
setTimeout(function(){ this.disabled = false; //定时器函数里面的this指向的是window }.bind(this),3000) } </script>
 

call  apply  bind 的异同点:


三、严格模式

3.1 严格模式的概念

ES5的严格模式是采用具有限制性JavaScript变体的一种方式,即在严格模式下运行JS代码 

严格模式在IE10以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略

严格模式对正常的JavaScript语义做了一些更改:

(1)消除了JavaScript语法的一些不合理、不严谨之处,减少了一些怪异行为

(2)消除代码运行的一些不安全之处,保证代码运行的安全

(3)提高编译器效率,增加运行速度

(4)禁用了ECMAScript的未来版本中可能会定义的一些语法,为未来新版本的JavaScript做好铺垫,比如一些保留字如Lclass,enum,export,extends,import,super不能做变量名

3.2 开启严格模式

(1)为整个脚本(script标签)开启严格模式

<script>
    'use strict'; // 下面的JS代码就会按照严格模式执行代码
</script>

有的script基本是严格模式,有的script脚本是正常模式,这样不利于文件合并,所以可以把整个脚本文件放在一个立即执行的匿名函数之中,这样独立创建一个作用于而不影响其他script脚本文件

<script>
    (function() {
        'use strict';
        var num = 10;
        function fn(){}
})();
</script>

(2)为某个函数开启严格模式

<script>
//此时只是给 fn 函数开启严格模式
    function fn() {
        'use strict';
//下面额代码按照严格模式执行 }
function fun() {
// 里面的还是按照普通模式执行 }
</script>

 

3.3 严格模式中的变化

1. 变量规定

(1)在正常模式中,如果一个变量没有声明就赋值,默认是全局变量,严格模式禁止这种用法,变量都必须先用var命令声明,然后再使用

(2)我们不能随意删除已经声明的变量,比如 delete 变量名,是不被允许的

2. 严格模式下 this指向问题

(1)以前在全局作用域函数中的this指向window对象

(2)严格模式下全局作用域中函数中的this是 undefined

(3)以前构造函数时不加new 也可以调用,当普通函数来进行调用,里面的this指向全局对象

(4)严格模式下,如果 构造函数不加 new进行调用,this会报错

function Star() {
    this.sex = '男';
}
Star();  //非严格模式下,把构造函数当普通函数进行调用,但是如果在严格模式下这样调用就会报错,因为严格模式下this是undefined

使用new实例化的构造函数指向创建的对象实例

(5)在严格模式下,定时器里面的 this 还是指向window

(6)事件,对象还是指向调用者

3. 函数变化

(1)严格模式下,函数不能有重名的参数

(2)严格模式下,禁止不在脚本或者函数层面上的函数声明(禁止在非函数的代码块中声明函数)

"use strict";
if (true) {
  function f() { } // !!! 语法错误
  f();
}

for (var i = 0; i < 5; i++) {
  function f2() { } // !!! 语法错误
  f2();
}

function baz() { // 合法
  function eit() { } // 同样合法
}

四、高阶函数

高阶函数:是对其他函数进行操作的函数,他接收函数作为参数或者将函数作为返回值输出

把函数当做参数传递给另外一个函数来使用,这就是回调函数


五、闭包

5.1 变量作用域

 全局变量和局部变量

1.函数内部可以使用全局变量

2.函数外部不可以使用局部变量

3.当函数执行完毕,本作用域内的局部变量会销毁

5.2 什么是闭包

闭包(closure)指的有权访问另外一个函数作用域中变量的函数——JavaScript高级程序设计

简单理解:一个作用域可以访问另外一个函数内部的局部变量;闭包是一个函数

function fn() {
    var num = 10;
    function fun() {
        console,log(num);
    }
    fun();
}
fn();

在上面的例子中,函数fun内部访问了函数fn里面的局部变量num,则num变量所在的函数就是闭包函数

5.3 闭包的作用

我们fn 外面的作用域可以访问fn 内部的局部变量

function fn() {
    var num = 10;
    function fun() {
        console.log(num);
    }
    return fun;
}
var f = fn();//类似于var f = function fun(){console.log(num);}
f();

首先 var f=fn() 调用函数fn(), 执行var num= 10;

接着由于没有调用fun()函数,直接到下一步,return fun;返回fun函数,因此,f就是fun函数

接着调用f () 函数,也就是调用了 fun 函数,然后就进入了fun函数内部,开始执行 console.log(num)这句话,由于这句话是在fn内部,是可以访问到num变量的,由此产生了闭包问题

上面可以简化为如下形式:

function fn() {
    var num = 10;
    return function() {
        console.log(num);
    };
}
var f = fn();//类似于var f = function fun(){console.log(num);}
f();//输出10

在一个函数中返回值是另外一个函数,则这个函数是一个高阶函数

闭包的主要作用:延伸了变量的作用范围


5.4 闭包的案例

(1)案例一:实现点击对应标签,打印对应索引号

<ul class="nav">
    <li>榴莲</li>
    <li>臭豆腐</li>
    <li>鲱鱼罐头</li>
    <li>大猪蹄子</li>
</ul>
// 1.我们可以利用动态添加属性的方式
var lis = document.querySelector('.nav').querySelectorAll('li');
for(var i = 0;i<lis.length;i++){
    lis[i].index = i;
    lis[i].onclick = function() {
         console.log(this.index);
    }
}
// 2.利用闭包的方式得到当前小li 的索引号
var lis = document.querySelector('.nav').querySelectorAll('li');
for(var i = 0;i<lis.length;i++){
    // 利用for循环创建了4 个立即执行函数
// 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量 (function(i){ //第二步:接受传递的参数 lis[i].onclick = function() {// 第三步:使用参数,并打印出来
console.log(i);
})(i);// 第一步:每次循环先把索引号传递到这里 }

()() 这是一个立即执行函数,第二个括号代表前面 立即执行函数 的调用,里面可以传递参数,我们把外面循环中的 i 当做参数传递进去,然后第一个括号里面的 立即执行函数 可以得到这个参数i,然后在这个函数内部调用这个参数 i

(2)案例二:利用定时器在3 秒钟之后打印标签中的内容

<ul class="nav">
    <li>榴莲</li>
    <li>臭豆腐</li>
    <li>鲱鱼罐头</li>
    <li>大猪蹄子</li>
</ul>
var lis = document.querySelector('.nav').querySelectorAll('li');
for(var i = 0;i<lis.length;i++){
    // 利用for循环创建了4 个立即执行函数
    // 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量
    (function(i){ //第二步:接受传递的参数
        setTimeout(function() {
            console.log(lis[i].innerHTML);// 第三步:打印第i个标签的内容
        },3000)
    })(i);// 第一步:每次循环先把索引号传递到这里
}

定时器这个函数用到了立即执行函数里面的变量(外面的立即执行函数就是闭包函数)

(3)案例三:打车价格


六、递归

6.1 递归函数

函数内部自己调用自己,这个函数就是递归函数

递归函数的作用和循环效果一样

很容易出现“栈溢出”错误,因此要加上 退出条件

七、深拷贝和浅拷贝

1.浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用(地址)

var obj = {
    id:1,
    name:'哈哈哈'
}
// 一般可以使用 for 循环实现浅拷贝
var o ={}; for(var k in obj) { //k 是属性名, obj[k]是属性值 o[k] = obj[k]; }

ES6新增方法可以实现浅拷贝

Object.assign(target, sources)
//如果对象里面又包含了更深层次的对象,使用浅拷贝只是拷贝了这个更深层次属性的地址
//如果在拷贝的变量中修改了这个深层次对象的内容,原来的变量也会发生改变
var
obj = { id:1, name:'哈哈哈', goods: { id:11, name:'啦啦啦啦' } }

 

如果修改了 o 或者 obj 任何一个变量的内容,两个值都会发生改变

2.深拷贝拷贝多层,每一级别的数据都会拷贝

针对更深层次的对象,会开辟新的内存地址

var obj = {
    id:1,
    name:'Andy',
    msg: {
        age: 18
    },
    color: ['pink','red']
};
var o = {};
function deepCopy(newobj,oldobj){
    for(var k in oldobj) {
        //判断我们的属性属于哪种数据类型
        // 1.获取属性值 oldobj[k]
        var item = oldobj[k];
        // 2.判断这个值是否是数组
        if(item instanceof Array) {
        newobj[k] = [];
        deepCopy(newobj[k], item)
        } else if(item instanceof Object) {
        // 3.判断这个值是否是对象
        newobj[k] = {};
        deepCopy(newobj[k], item)
        } else {
// 4. 属于简单数据类型 newobj[k]
= item; }
} }
原文地址:https://www.cnblogs.com/ccv2/p/12701574.html