函数进阶
函数回顾
函数的定义方式
-
函数声明
function foo () {
}
-
函数表达式
var foo = function () {
}
函数声明与函数表达式的区别
-
函数声明必须有名字
-
函数声明会函数提升,在预解析阶段就已创建,声明前后都可以调用
-
函数表达式类似于变量赋值
-
函数表达式可以没有名字,例如匿名函数
-
函数表达式没有变量提升,在执行阶段创建,必须在表达式执行之后才可以调用
函数的调用方式
-
普通函数
function foo () {
}
foo()
-
构造函数
function Person (name,age) {
thia.name = name;
this.age = age
...
}
new Person();
-
对象方法
var obj = {
name:"zhangsan",
age:12,
eat:function(){
..
}
}
obj.eat();
函数内 this
指向的不同场景
函数的调用方式决定了 this
指向的不同:
调用方式 | 非严格模式 | 备注 |
---|---|---|
普通函数调用 | window | 严格模式下是 undefined |
构造函数调用 | 实例对象 | 原型方法中 this 也是实例对象 |
对象方法调用 | 该方法所属对象 | 紧挨着的对象 |
事件绑定方法 | 绑定事件对象 | |
定时器函数 | window |
这就是对函数内部 this 指向的基本整理,写代码写多了自然而然就熟悉了。
<ul id="ul1">
<li>我是第1个li</li>
<li>我是第2个li</li>
<li>我是第3个li</li>
<li>我是第4个li</li>
<li>我是第5个li</li>
</ul>
<script>
// 普通函数内部this指向
// 这是全局函数,挂载在window上
var a = 100;
function fn1() {
// 所以在这个函数内部使用this,指向的就是window
console.log(this, this.a);
}
fn1();
console.log("-----------------------------");
// 构造函数调用,构造函数里面的this,是实例对象
// 当我们使用new来调用构造函数的时候,会隐式创建一个对象,然后把这个对象给this,最后把this反馈出去
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHello = function () {
console.log(this.name + "说:Hello");
}
}
var p1 = new Person("张三", 18);
p1.sayHello();
console.log("-----------------------------");
// 对象方法调用,this就是该方法所属对象
var obj = {
name: "李四",
// this代表的是window
age: a,
// 对象方法里面的this,代表的就是方法所属对象
sayHello: function () {
console.log(this.name + "说:Hello");
}
}
obj.sayHello();
console.log(obj.age);
console.log("-----------------------------");
// 事件绑定方法
// document.onclick = function () {
// // 事件函数里面的this,代表事件的当前对象
// console.log(this)
// }
// 定时器函数
setTimeout(function () {
console.log(this);
}, 1000)
// 获取li的下标
var lis = document.querySelectorAll("#ul1>li");
for (var i = 0; i < lis.length; i++) {
var li = lis[i];
li.index = i;
//在鼠标函数里面,this是点击的li
li.onclick = function () {
// 通过变量来保存this
var _this = this;
// 1s之后,输出li的下标
setTimeout(function () {
// 因为在定时器里面,this是window
console.log(_this.index);
}, 1000)
}
}
</script>
call、apply、bind
例子1:
// window下面定义的两个全局变量
var name = "小王",
age = 17;
var obj = {
name: "小张",
// 这里的this是window
objAge: this.age,
myFun: function () {
// 对象函数里面的this就是函数所在的对象,obj
console.log(this.name + "年龄" + this.age);
}
}
console.log(obj.objAge); // 17
obj.myFun(); // 小张年龄 undefined
例子2:
var fav = "小张";
function shows() {
console.log(this.fav)
}
shows(); // 小张
那了解了函数 this 指向的不同场景之后,我们知道有些情况下我们为了使用某种特定环境的 this 引用, 这时候时候我们就需要采用一些特殊手段来处理了,例如我们经常在定时器外部备份 this 引用,然后在定时器函数内部使用外部 this 的引用。
然而实际上对于这种做法我们的 JavaScript 为我们专门提供了一些函数方法用来帮我们更优雅的处理函数内部 this 指向问题。
这就是接下来我们要学习的 call、apply、bind 三个函数方法。
语法
call讲解:
call()
方法调用一个函数, 其具有一个指定的 this
值和分别地提供的参数(参数的列表)。
注意:该方法的作用和 apply()
方法类似,只有一个区别,就是 call()
方法接受的是若干个参数的列表,而 apply()
方法接受的是一个包含多个参数的数组。
语法:
fun.call(thisArg[, arg1[, arg2[, ...]]])
参数:
-
thisArg
-
在 fun 函数运行时指定的 this 值
-
如果指定了 null 或者 undefined 则内部 this 指向 window
-
-
arg1, arg2, ...
-
指定的参数列表
-
apply
apply()
方法调用一个函数, 其具有一个指定的 this
值,以及作为一个数组(或类似数组的对象)提供的参数。
注意:该方法的作用和 `call()` 方法类似,只有一个区别,就是 `call()` 方法接受的是若干个参数的列表,而 `apply()` 方法接受的是一个包含多个参数的数组。
语法:
fun.apply(thisArg, [argsArray])
参数:
-
thisArg
-
argsArray
apply()
与 call()
非常相似,不同之处在于提供参数的方式。 apply()
使用参数数组而不是一组参数列表。例如:
fun.apply(this, ['eat', 'bananas'])
bind
bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。 当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。 一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
语法:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
参数:
-
thisArg
-
当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。
-
-
arg1, arg2, ...
-
当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。
-
返回值:
返回由指定的this值和初始化参数改造的原函数拷贝。
测试
1、call()、apply()、bind() 都是用来重定义 this 这个对象的!
var name = "小王",
age = 17;
var obj = {
name: "小张",
objAge: this.age,
myFun: function () {
console.log(this.name + "年龄" + this.age);
}
}
var db = {
name: "德邦总管",
age: 99
}
obj.myFun.call(db); // 德邦总管年龄 99
obj.myFun.apply(db); // 德邦总管年龄 99
obj.myFun.bind(db)(); // 德邦总管年龄 99
以上出了 bind 方法后面多了个 () 外 ,结果返回都一致!
由此得出结论,bind 返回的是一个新的函数,你必须调用它才会被执行。
2、对比call 、bind 、 apply 传参情况下
var name = "小王",
age = 17;
var obj = {
name: "小张",
objAge: this.age,
myFun: function (fm, to) {
console.log(this.name + " 年龄 " + this.age, " 来自 " + fm + " 去往 " + to);
}
}
var db = {
name: "德邦总管",
age: 99
}
obj.myFun.call(db, '成都', '上海'); // 德邦总管 年龄 99 来自 成都去往上海
obj.myFun.apply(db, ['成都', '上海']); // 德邦总管 年龄 99 来自 成都去往上海
obj.myFun.bind(db, '成都', '上海')(); // 德邦总管 年龄 99 来自 成都去往上海
obj.myFun.bind(db, ['成都', '上海'])(); // 德邦总管 年龄 99 来自 成都, 上海去往 undefined
应用
call的应用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<span>1</span>
<span>2</span>
<span>3</span>
<script>
// 获取到页面的所有span,得到HTMLCollection,无论是HTMLCollection还是NodeList,我们都称之为伪数组
// 伪数组本质是一个对象,只是模拟的数组的特性
// 用下标作为对象的key,并且也提供的length
var spans = document.getElementsByTagName("span");
console.log(spans)
// spans.shift(); // spans.shift is not a function
var arr = ["zhangsan", "lis", "wangwu"]
console.log(arr)
// getElementsByTagName()
// 伪数组
var obj = {
0: 100,
1: 10,
2: 11,
3: 20,
length: 4
};
console.log("伪数组下标2的值:" + obj['2']);
// 向伪数组添加一个值
// obj.push(30); // obj.push is not a function
// obj['4'] = 30;
// obj.length++;
// console.log(obj);
// push(参数) 向当前数组的末尾添加一个参数
// push() this[最后下标+1] = 值; length++;
Array.prototype.push.call(obj, 330); // this[最后下标+1] = 值;
// Array.prototype.splice.call(obj, 0, 3);
console.log(obj);
var obj = {
name: 'zs'
};
console.log(obj.toString()); // [object object]
var arr = [5, 9];
console.log(arr.toString()); // 5,9
console.log(Object.prototype.toString.call(arr));
</script>
</body>
</html>
apply应用
// fn.apply(,[])
// Math.max(3, 5, 6);
var arr = [5, 10, 1, 3, 6];
// Math.max不能求数组中的最大值
// console.log(Math.max(arr));
console.log(Math.max.apply(null, arr));
console.log(Math.max.apply(Math, arr));
// console.log(1, 2, 3);
// console.log(arr);
bind应用
// 3 bind的应用
function Bloomer() {
this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
// 1秒后调用declare函数
Bloomer.prototype.bloom = function () {
window.setTimeout(this.declare.bind(this), 1000);
};
Bloomer.prototype.declare = function () {
console.log('我有 ' + this.petalCount + ' 朵花瓣!');
};
var test = new Bloomer();
test.bloom();
<ul id="ul1">
<li>我是第1个li</li>
<li>我是第2个li</li>
<li>我是第3个li</li>
<li>我是第4个li</li>
<li>我是第5个li</li>
</ul>
<script>
// 获取li的下标
var lis = document.querySelectorAll("#ul1>li");
for (var i = 0; i < lis.length; i++) {
var li = lis[i];
li.index = i;
//在鼠标函数里面,this是点击的li
li.onclick = function () {
// // 通过变量来保存this
// var _this = this;
// 1s之后,输出li的下标
setTimeout(function () {
// 因为在定时器里面,this是window
console.log(this.index, this.innerHTML);
}.bind(this), 1000)
}
}
</script>
小结
-
call 和 apply 特性一样
-
都是用来调用函数,而且是立即调用
-
但是可以在调用函数的同时,通过第一个参数指定函数内部
this
的指向 -
call 调用的时候,参数必须以参数列表的形式进行传递,也就是以逗号分隔的方式依次传递即可
-
apply 调用的时候,参数必须是一个数组,然后在执行的时候,会将数组内部的元素一个一个拿出来,与形参一一对应进行传递
-
如果第一个参数指定了
null
或者undefined
则内部 this 指向 window
-
-
bind
-
可以用来指定内部 this 的指向,然后生成一个改变了 this 指向的新的函数
-
它和 call、apply 最大的区别是:bind 不会调用
-
bind 支持传递参数,它的传参方式比较特殊,一共有两个位置可以传递
-
在 bind 的同时,以参数列表的形式进行传递
-
在调用的时候,以参数列表的形式进行传递
-
那到底以谁 bind 的时候传递的参数为准呢还是以调用的时候传递的参数为准
-
两者合并:bind 的时候传递的参数和调用的时候传递的参数会合并到一起,传递到函数内部
-
-
函数的其它成员
-
arguments
-
实参集合
-
-
caller
-
函数的调用者
-
-
length
-
形参的个数
-
-
name
-
函数的名称
-
function fn(x, y, z) {
console.log(fn.length) // => 形参的个数
console.log(arguments) // 伪数组实参参数集合
console.log(arguments.callee === fn) // 函数本身
console.log(fn.caller) // 函数的调用者
console.log(fn.name) // => 函数的名字
}
function f() {
fn(10, 20, 30)
}
f()
高阶函数
-
函数可以作为参数
-
函数可以作为返回值
作为参数
function eat (callback) {
setTimeout(function () {
console.log('吃完了')
callback()
}, 1000)
}
eat(function () {
console.log('去唱歌')
})
作为返回值
function genFun (type) {
return function (obj) {
return Object.prototype.toString.call(obj) === type
}
}
var isArray = genFun('[object Array]')
var isObject = genFun('[object Object]')
console.log(isArray([])) // => true
console.log(isArray({})) // => true