函数进阶
函数的定义方式
- 命名函数
function foo () {}
- 匿名函数
var foo = function(){}
函数的调用方式
- 普通函数
function foo () {
console.log("普通函数的调用")
}
foo();//foo.call()
- 构造函数
function Person(){
}
var p = new Person()
- 对象方法
var obj = {
saiHi:function(){
console.log("hi")
}
}
obj.saiHi()
- 绑定事件函数
btn.onclick = function(){}
- 定时器函数
setInterval(function(){
},1000)
- 立即执行函数
(function(){})();
函数内 this
指向的不同场景
函数的调用方式决定了 this
指向的不同:
调用方式 | 非严格模式 | 备注 |
---|---|---|
普通函数调用 | window | 严格模式下是 undefined |
构造函数调用 | 实例对象 | 原型方法中 this 也是实例对象 |
对象方法调用 | 该方法所属对象 | 紧挨着的对象 |
事件绑定方法 | 绑定事件对象 | |
定时器函数 | window | |
立即执行函数 | window |
这就是对函数内部 this 指向的基本整理,写代码写多了自然而然就熟悉了。
apply方法
apply()
方法调用一个函数, 其具有一个指定的 this
值,以及作为一个数组(或类似数组的对象)提供的参数。
注意:该方法的作用和 `call()` 方法类似,只有一个区别,就是 `call()` 方法接受的是若干个参数的列表,而 `apply()` 方法接受的是一个包含多个参数的数组。
语法:
fun.apply(thisArg, [argsArray])
参数:
thisArg
argsArray
apply()
与 call()
非常相似,不同之处在于提供参数的方式。
apply()
使用参数数组而不是一组参数列表。例如:
fun.apply(this, ['eat', 'bananas'])
function foo(arr){
console.log(this);//window
console.log(arr);//打印的不是数组,是字符串pink
}
foo.apply(null,['pink'])
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
var arr = [3, 42, 31, 3, 5, 465, 6, 99]
function foo(arr) {
console.log(this);//window
console.log(arr);//打印的不是数组,是字符串pink
}
foo.apply(null, ['pink'])
var max = Math.max.apply(null, arr)
console.log(max);
</script>
</body>
</html>
bind
bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。
当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
语法:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
参数:
- thisArg
- 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。
- arg1, arg2, …
- 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。
返回值:
返回由指定的this值和初始化参数改造的原函数拷贝。
示例1:
this.x = 9;
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 返回 81
var retrieveX = module.getX;
retrieveX(); // 返回 9, 在这种情况下,"this"指向全局作用域
// 创建一个新函数,将"this"绑定到module对象
// 新手可能会被全局的x变量和module里的属性x所迷惑
var boundGetX = retrieveX.bind(module);
boundGetX(); // 返回 81
示例2:
function LateBloomer() {
this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
// Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
window.setTimeout(this.declare.bind(this), 1000);
};
LateBloomer.prototype.declare = function() {
console.log('I am a beautiful flower with ' +
this.petalCount + ' petals!');
};
var flower = new LateBloomer();
flower.bloom(); // 一秒钟后, 调用'declare'方法
小结
- call 和 apply 特性一样
- 都是用来调用函数,而且是立即调用
- 但是可以在调用函数的同时,通过第一个参数指定函数内部
this
的指向 - call 调用的时候,参数必须以参数列表的形式进行传递,也就是以逗号分隔的方式依次传递即可
- apply 调用的时候,参数必须是一个数组,然后在执行的时候,会将数组内部的元素一个一个拿出来,与形参一一对应进行传递
- 如果第一个参数指定了
null
或者undefined
则内部 this 指向 window
- bind
- 可以用来指定内部 this 的指向,然后生成一个改变了 this 指向的新的函数
- 它和 call、apply 最大的区别是:bind 不会调用
- bind 支持传递参数,它的传参方式比较特殊,一共有两个位置可以传递
-
- 在 bind 的同时,以参数列表的形式进行传递
-
- 在调用的时候,以参数列表的形式进行传递
- 那到底以谁 bind 的时候传递的参数为准呢还是以调用的时候传递的参数为准
- 两者合并:bind 的时候传递的参数和调用的时候传递的参数会合并到一起,传递到函数内部
-
高阶函数
- 函数可以作为参数
- 函数可以作为返回值
作为参数
function sum(a,b,callback){
console.log(a+b);
callback && callback();//有参数才调用
}
sum(1,2,function(){
console.log("求和之后调用的!")
})
作为返回值
function foo(){
return function(){}
}
foo()
函数闭包
什么是闭包
闭包就是能够读取其他函数内部变量的函数,
由于在 Javascript 语言中,只有函数内部的子函数才能读取局部变量,
因此可以把闭包简单理解成 “定义在一个函数内部的函数”。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的用途:
- 可以在函数外部读取函数内部成员
- 让函数内成员始终存活在内存中
一些关于闭包的例子
示例1:
function foo() {
var num = 10
function get() {
console.log(10)
}
return get;
}
var get = foo();
get() //获取到get里的值,相当于在外作用域访问到foo函数里面的值
//简写
function foo() {
var num = 10
return function () {
console.log(10)
};
}
var get = foo();
get()
闭包的思考题
- 有没有闭包的产生
- 结果是什么?
思考题 1:
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
};
console.log(object.getNameFunc()())//The Window
//object.getNameFunc()() => var f = object.getNameFunc() f() this指向的是window 全局属性是挂载到window下的
//没有闭包的产生,函数内没有局部变量
思考题 2:
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
var that = this;//此时的this指向的是调用者=》object
return function () {
return that.name;
};
}
};
console.log(object.getNameFunc()())//My Object,有闭包的产生,使用了局部变量
小结
- 闭包是一个函数,一个作用域可以访问另外一个函数的局部变量
函数递归
- 函数内部自己调用自己,这个函数就是递归
- 作用和循环一样,但是容易发生死循环(栈溢出)
递归执行模型
function fn(){
fn()
}
fn()//死递归
必须加退出条件
var num = 1;
function fn(){
console.log("我是递归")
if(num>10){
return;
}
num++;
fn()
}
fn()
举个栗子:
计算阶乘的递归函数
//求1到n的阶乘 1*2*3...*n
function fn (num) {
if (num <= 1) {
return 1
} else {
return num * fn(num - 1)
}
}
console.log(fn(3))
//详细思路
//1、return 3*fn(3-1)
//2、return 3*fn(2) 此时必须返回的是结果 不能是函数
//3、继续调用 return 3*(2*fn(2-1))
//4、return 3*(2*fn(1))
//5、return 3*(2*1)
//6、return 3*(2)
//结果 6
斐波那契数列
// 1、1、2、3、5、8
// 用户输入一个数字 n 求出这个数字对应的序列值
// 我们只需要知道用户输入的n,那么前面两项(n-1 n-2)
function fb(n) {
if(n==1 || n==2){
return 1;
}
return fb(n-1)+fb(n-2);
}
console.log(fb(3));//2
console.log(fb(6));//8
根据输入的id,返回数据对象
数据
var data = [{
id:1,
name:'家电',
goods:[{
id:11,
gname:'冰箱'
},{
id:12,
gname:'洗衣机'
}]
},{
id:2,
name:'服饰'
}]
//我们想要输入id,就可以获取数据对象
//forEach遍历
function getId(json, id) {
json.forEach(element => {
if(element.id == id){
console.log(element);
}else if(element.goods && element.goods.length>0){
//里面有goods 这个数组 并且 数组的长度不为0
getId(element.goods,id)
}
});
}
getId(data, 1);
getId(data, 12);
不打印,而是获取数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
var data = [{
id: 1,
name: '家电',
goods: [{
id: 11,
gname: '冰箱'
}, {
id: 12,
gname: '洗衣机'
}]
}, {
id: 2,
name: '服饰'
}]
//我们想要输入id,就可以获取数据对象
//forEach遍历
function getId(json, id) {
var o ={};
json.forEach(element => {
if(element.id == id){
// console.log(element);
o = element;
return o;
}else if(element.goods && element.goods.length>0){
//里面有goods 这个数组 并且 数组的长度不为0
o = getId(element.goods,id)
}
});
return o;
}
console.log(getId(data, 1));
console.log(getId(data, 12));
</script>
</body>
</html>
浅拷贝和深拷贝
- 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用(地址值)
- 深拷贝拷贝多层,每一级别的数据都会拷贝
浅拷贝
//浅拷贝
var data = {
name:'tom',
sex:"男"
}
var o = {}
for(var k in data){
o[k] = data[k]
}
console.log(o);
var data1 = {
name:'tom',
sex:"男",
msg:{
age:20
}
}
var o1 = {}
for(var k1 in data1){
o1[k1] = data1[k1]
}
console.log(o1);//age是20,但是浅拷贝 拷贝是地址值,修改o1的年龄,data1也随之修改
o1.msg.age = 100
console.log(data1);//100
ES6新语法
var data1 = {
name:'tom',
sex:"男",
msg:{
age:20
}
}
var o1 = {}
Object.assign(o1,data1)
console.log(o1);//age是20,但是浅拷贝 拷贝是地址值,修改o1的年龄,data1也随之修改
o1.msg.age = 100
console.log(data1);//100
深拷贝
var data = {
name: 'tom',
sex: "男",
msg: {
age: 20
},
color:['pink','red']
}
var o = {}
function deepCopy(newObj, oldObj) {
for(var k in oldObj){
//如果是简单类型 直接拷贝,复杂类型进入后在拷贝
//获取属性值oldObj[k]
var item = oldObj[k]
if(item instanceof Array){
newObj[k] = []
deepCopy(newObj[k],item)
}else if(item instanceof Object){
newObj[k] = {}
deepCopy(newObj[k],item)
}else{
newObj[k] = item;
}
}
}
deepCopy(o,data);
console.log(o);
o.msg.age =100;
console.log(data);//没有被修改
递归应用场景
- 深拷贝
- 菜单树
- 遍历 DOM 树
wx:410473720