JavaScript高级笔记_003_函数进阶
函数进阶
课程链接:
https://www.bilibili.com/video/BV1KJ411x7X7?p=50
函数的定义和调用
函数的定义方式
-
函数声明方式
function
关键字(命名函数,自定义函数) -
函数表达式(匿名函数)
-
new Function('参数1', '参数2', '函数体')
var f = new Function('a', 'b', 'console.log(a + b)'); f(1, 2);
-
Function里面的参数必须是字符串格式
-
第三种方式执行效率低,也不方便书写,因此较少使用
-
所有函数都是****** 的实例(对象)**
-
函数也属于对象
-
函数的调用方式
-
普通函数
function fn() { console.log('人生巅峰'); } fn(); // 或者 fn.call();
-
对象的方法
var o = { sayHi: function() { console.log('人生的巅峰'); } }
-
构造函数
function Star() { console.log('人生的巅峰'); } new Star();
-
绑定事件函数
btn.onclick = function() {};
-
定时器函数
// 这个函数是定时器1秒钟自动调用一次 setInterval(function(){}, 1000);
-
立即执行函数
(function() { console.log('人生巅峰'); })(); // 立即执行函数自动调用
this
改变函数内this 的指向
这些
this
的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this
的指向不同一般指向我们的调用者
调用方式 | ****** 指向** |
---|---|
普通函数调用 | window |
构造函数调用 | 实例对象 原型对象里面的 方法也指向实例对象 |
对象方法调用 | 该方法所属对象 |
事件绑定方法 | 绑定事件对象 |
定时器函数 | window |
立即执行函数 | window |
JavaScript
为我们专门提供了一些函数方法来帮我们更优雅的处理函数内部的this
指向问题,常用的有bind()
,call()
,apply()
三种方法
-
call()
方法call()
方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的this
的指向。语法
fun.call(thisArgm, arg1, arg2, ...);
例子
var o = { name: 'andy' } function fn() { console.log(this); }; fn.call(o);
****主要作用可以实现继承
function Father(uname, age, sex) { this.uname = uname; this.age = age; this.sex = sex; } function Son(uname, age, sex) { Father.call(this, uname, age, sex); } var son = new Son('刘德华', 18, '男'); console.log(son);
-
apply
方法apply()
方法调用一个函数。简单理解为调用函数的方式,但是它可以改变this
指向语法
fun.apply(thisArg, [argsArray]);
-
thisArg
:在fun函数运行时指定的this
值 -
argsArray
:传递的值,必须包含在数组里面(伪数组) -
返回值就是函数的返回值,因为它就是调用函数
例子
var o = { name: 'andy' }; function fn(arr) { console.log(this); console.log(arr); } fn.apply(o, ['andy']);
****** 的主要应用**:比如说我们可以利用
apply
借助数学内置对象求最大值var arr = [1, 66, 3, 99, 4]; var max = Math.max.apply(Math, arr); var min = Math.min.apply(Math, arr); concole.log(max); concole.log(min);
-
-
****** 方法(重点)**
bind()
方法不会调用函数。但是能改变函数内部this
指向语法
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); } // 不会调用原来的函数,可以改变原函数内部的this var f = fn.bind(o, 1, 2); // 返回的是原函数改变this之后产生的新函数 f();
-
不会调用原来的函数,可以改变原来函数的内部的this指向
-
返回的是原函数改变this 之后产生的新函数
-
如果有的函数不需要立即调用,但是又想改变这个函数内部的this指向,此时用bind
-
我们有一个按钮,当我们点击了之后,就禁用这个按钮,3秒之后开启这个按钮
var btn = document.querySelector('button'); btn.onclick = functionn() { this.disabled = ture; // 这个this指向的是btn这个按钮 setTimeout(function() { // 定时器函数里面的this指向的是window this.disabled = false; }.bind(this), 3000) // 这个this指向的是btn这个对象 }
-
严格模式
什么是严格模式
JavaScript除了提供正常模式外,还提供了严格模式(strict mode)。ES5的严格模式是采用具有限制性javascript变体的一种方式,即在严格的条件下运行JS代码。
严格模式在IE10以上版本的浏览器才会被支持,旧版本浏览器中会被忽略
严格模式对正常的javascript语义做了一些修改:
-
消除了javascript语法的一些不合理,不严谨之处,减少了 一些怪异行为
-
消除了代码运行的一些不安全之处,保证代码运行的安全
-
提高编译器效率,增加运行速度
-
禁用了在ECMAScript的未来版本中可能会定义的一些语法,为未来新版本的javascript做好铺垫。比如一些保留字如:class enum export extends inport super 不能做变量名
开启严格模式
严格模式可以应用到整个脚本中或者个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况
-
为脚本开启严格模式
需要在所有语句之前放一个特定语句"use strict";(或’use strict’;)
'use strict'; // 下面JS代码就会按照严格模式执行代码
// 为整个脚本(script标签)开启严格模式的两种方法 <script> 'use strict'; </script> <script> (function(){ 'use strict'; })() </script>
-
为函数开启严格模式
要给某个函数开启严格模式,需要把"use strict";(或’use strict’)声明放在函数体所有语句之前
<script> function fn() { 'use strict'; ... } function fun() { ... } </script>
严格模式中的变化
严格模式对JavaScript的语法和行为都做了一些改变
-
变量规定
-
在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用var命令声明,然后再使用
-
严禁删除已经声明变量、例如,
delete x
;是语法错误的
-
-
严格模式下****** 指向问题**
-
以前在全局作用域函数中this指向window对象
-
严格模式下全局作用域函数中this指向undefined
-
以前构造函数时不用加new也可以调用,当普通函数,this指向全局对象
-
严格模式下,如果构造函数不加new调用,this会报错
-
new实例化的构造函数指向创建的对象实例
-
定时器的this还是指向window
-
事件对象还是指向调用者
-
-
函数变化
-
函数不能有重名的参数
-
函数必须声明在顶层,新版本的javascript会引入“块级作用域”(ES6中已引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数
-
高阶函数
高级函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出
function fn() {
callback && callback();
}
fn(function(){alert("HELLO");});
function fn() {
return function() {
}
}
fn();
此时fn
就是一个高阶函数
函数也是一种数据类型,同样可以作为参数,传递给拎一个参数使用。最典型的就是作为回调函数
闭包
变量作用域
变量根据作用域的不同分为两种:全局变量和局部变量
-
函数内部可以使用全局变量
-
函数外部不可以使用局部变量
-
函数执行完毕,本作用域内的局部变量会销毁
什么是闭包
闭包(closure)指有权访问另一个函数 作用域中变量的函数
简单理解就是,一个作用域可以访问另一个函数内部的局部变量
function fn() {
var num = 10;
function fun() {
console.log(num);
}
fun();
}
fn();
// 此时fn就是闭包
闭包的主要作用:延伸了变量的作用范围
闭包的案例
-
循环注册点击事件
<div class = 'nav'> <li>榴莲</li> <li>臭豆腐</li> <li>鲱鱼罐头</li> <li>大猪蹄子</li> </div>
// 获取小li的方式 // 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的索引号 for(var i = 0; i < lis.length; i++) { (function(i) { lis[i].onclick = function() { console.log(i); } })(i) // 把 i 传入立即执行函数 } // 此时的闭包并不好
-
循环中的
setTimeout()
<div class = 'nav'> <li>榴莲</li> <li>臭豆腐</li> <li>鲱鱼罐头</li> <li>大猪蹄子</li> </div>
// 闭包应用3秒之后打印所有li元素的内容 var lis = var lis = document.querySelector('.nav').querySelectorAll('li'); for(var i = 0; i < lis.length; i++) { (function(i) { setTimeout(function(){ console.log(lis[i].innerHTML); }, 3000) })(i); }
-
计算打车价格
// 打车起步价13(3公里内),之后没多一公里增加5块钱,用户输入公里数就可以计算打车价格 // 如果有拥堵情况,总价格多收取10块钱拥堵费 var car = (function() { var start = 13; // q起步价 var total = 0; // 总价 return { // 正常的总价 pricr: function(n) { if(n <= 3) { total = start; } else { total = start + (n - 3) * 5; } return total; }, // 拥堵之后的费用 yd: function(flag) { return flag ? total + 10 : total; } } })(); console.log(car.price(5)); // 23 console.log(car.yd(ture)); // 33 console.log(car.price(1)); // 13 console.log(car.yd(false)); // 13
递归
什么是递归?
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
简单说:自己调用自己
递归函数的作用和循环效果一样
由于递归很容易出现“栈溢出”错误(stack overflow),所以必须加退出条件return
// 打印六句话
var num = 0;
function fn() {
console.log('我要打印六句话');
if(num === 6) {
return; // 必须加return
}
num++;
fn();
}
fn();
利用递归求数学题
-
求1*2*3*4*5*…*n 阶乘
function fn(n) { if(n === 1 || n === 0) { return 1; } return n * fn(n - 1); } console.log(fn(4));
-
求斐波那契数列
function fn(n) { if(n === 2 || n === 1) { return 1; } return fn(n - 1) + fn(n - 2); } console.log(fn(4));
利用递归求:根据id返回对应的数据对象
var data = [{
id: 1,
name: '家电’,
goods: [{
id: 11,
gname: '冰箱'
}, {
id: 12,
gname: '洗衣机'
}]
}, {
id: 2,
name: '服饰'
}];
// 我们想要做输入id号,就可以返回的数据对象
// 1. 利用forEach去遍历里面的每一个对象
function getId(json, id) {
var o = {};
json.forEach(function(item) {
if(item.id == id) {
// console.log(item);
return item;
} else if(item.goods && item.goods.length > 0) {
// 2. 得到里层的数据
0 = getId(item.goods, id);
}
});
return o;
}
console.log(getId(data, 11));
浅拷贝和深拷贝
-
浅拷贝只是拷贝一层,更深层次对象的值拷贝引用
-
深拷贝拷贝多层,每一级别的数据都会拷贝
// 浅拷贝拷贝更层次的时候只是拷贝地址 var obj = { id: 1, name: 'andy' }; var o = {}; for(var k in obj) { // k是属性名,obj[k]属性值 o[k] = obj[k]; } // 浅拷贝的语法糖 Object.assign(o, obj); // 把obj拷贝给o
// 深拷贝 var obj = { id: 1, name: 'andy', mdg: { age: 18 }, color: [ 'red', 'green' ] }; 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, item); } else if(item instanceof Object) { // 3. 判断这个值是否是对象 newObj[k] = {}; deepCopy(newObj[k], item); } else { // 4. 属于简单数据类型 newObj[k] = item; } } } deepCopy(o, obj); // 数组也属于对象