目录
1 函数的定义方式
定义函数的方式有三种:
- 函数声明
- 函数表达式
new Function
(一般不用)
1.1 函数声明
-
// 函数的声明
-
function fn() {
-
console.log(
"我是JS中的一等公民-函数!!!哈哈");
-
}
-
fn();
1.2 函数表达式
函数表达式就是将一个匿名函数赋值给一个变量。函数表达式必须先声明,再调用。
-
// 函数表达式
-
var fn =
function() {
-
console.log(
"我是JS中的一等公民-函数!!!哈哈");
-
};
-
fn();
1.3 函数声明与函数表达式的区别
- 函数声明必须有名字。
- 函数声明会函数提升,在预解析阶段就已创建,声明前后都可以调用。
- 函数表达式类似于变量赋值。
- 函数表达式可以没有名字,例如匿名函数。
- 函数表达式没有变量提升,在执行阶段创建,必须在表达式执行之后才可以调用。
下面是一个根据条件定义函数的例子:
-
if (
true) {
-
function f () {
-
console.log(
1)
-
}
-
}
else {
-
function f () {
-
console.log(
2)
-
}
-
}
以上代码执行结果在不同浏览器中结果不一致。我们可以使用函数表达式解决上面的问题:
-
var f
-
-
if (
true) {
-
f =
function () {
-
console.log(
1)
-
}
-
}
else {
-
f =
function () {
-
console.log(
2)
-
}
-
}
函数声明如果放在if-else的语句中,在IE8的浏览器中会出现问题,所以为了更好的兼容性我们以后最好用函数表达式,不用函数声明的方式。
1.4 构造函数Function(了解即可,一般不用)
在前面的学习中我们了解到函数也是对象。注意:函数是对象,对象不一定是函数,对象中有__proto__原型,函数中有prototype原型,如果一个东西里面有prototype,又有__proto__,说明它是函数,也是对象。
-
function F1() {}
-
-
console.dir(F1);
// F1里面有prototype,又有__proto__,说明是函数,也是对象
-
-
console.dir(
Math);
// Math中有__proto__,但是没有prorotype,说明Math不是函数
对象都是由构造函数创建出来的,函数既然是对象,创建它的构造函数又是什么呢?事实上所有的函数实际上都是由Function构造函数创建出来的实例对象。
所以我们可以使用Function构造函数创建函数。
语法:new Function(arg1,arg2,arg3..,body);
arg是任意参数,字符串类型的。body是函数体。
-
// 所有的函数实际上都是Function的构造函数创建出来的实例对象
-
var f1 =
new
Function(
"num1",
"num2",
"return num1+num2");
-
console.log(f1(
10,
20));
-
console.log(f1.__proto__ ==
Function.prototype);
-
-
// 所以,函数实际上也是对象
-
console.dir(f1);
-
console.dir(
Function);
2 函数的调用方式
- 普通函数
- 构造函数
- 对象方法
-
// 普通函数
-
function f1() {
-
console.log(
"我是普通函数");
-
}
-
f1();
-
-
// 构造函数---通过new 来调用,创建对象
-
function F1() {
-
console.log(
"我是构造函数");
-
}
-
var f =
new F1();
-
-
// 对象的方法
-
function Person() {
-
this.play =
function() {
-
console.log(
"我是对象中的方法");
-
};
-
}
-
var per =
new Person();
-
per.play();
3 函数内 this
的指向
函数的调用方式决定了 this
指向的不同:
调用方式 | 非严格模式 | 备注 |
---|---|---|
普通函数调用 | window | 严格模式下是 undefined |
构造函数调用 | 实例对象 | 原型方法中 this 也是实例对象 |
对象方法调用 | 该方法所属对象 | 紧挨着的对象 |
事件绑定方法 | 绑定事件对象 | |
定时器函数 | window |
-
// 普通函数
-
function f1() {
-
console.log(
this);
// window
-
}
-
f1();
-
-
// 构造函数
-
function Person() {
-
console.log(
this);
// Person
-
// 对象的方法
-
this.sayHi =
function() {
-
console.log(
this);
// Person
-
};
-
}
-
// 原型中的方法
-
Person.prototype.eat =
function() {
-
console.log(
this);
// Person
-
};
-
var per =
new Person();
-
console.log(per);
// Person
-
per.sayHi();
-
per.eat();
-
-
// 定时器中的this
-
setInterval(
function() {
-
console.log(
this);
// window
-
},
1000);
4 call、apply、bind
了解了函数 this 的指向之后,我们知道在一些情况下我们为了使用某种特定环境的 this 引用,需要采用一些特殊手段来处理,例如我们经常在定时器外部备份 this 引用,然后在定时器函数内部使用外部 this 的引用。
然而实际上 JavaScript 内部已经专门为我们提供了一些函数方法,用来帮我们更优雅的处理函数内部 this 指向问题。这就是接下来我们要学习的 call、apply、bind 三个函数方法。call()、apply()、bind()这三个方法都是是用来改变this的指向的。
4.1 call,apply
call()
方法调用一个函数, 其具有一个指定的 this
值和分别地提供的参数(参数的列表)。apply()
方法调用一个函数, 其具有一个指定的 this
值,以及作为一个数组(或类似数组的对象)提供的参数。
注意:call()
和 apply()
方法类似,只有一个区别,就是 call()
方法接受的是若干个参数的列表,而 apply()
方法接受的是一个包含多个参数的数组。
call语法:
fun.call(thisArg[, arg1[, arg2[, ...]]])
call参数:
-
thisArg
- 在 fun 函数运行时指定的 this 值
- 如果指定了 null 或者 undefined 则内部 this 指向 window
-
arg1, arg2, ...
- 指定的参数列表
apply语法:
fun.apply(thisArg, [argsArray])
apply参数:
thisArg
argsArray
apply()
与 call()
相似,不同之处在于提供参数的方式。apply()
使用参数数组而不是一组参数列表。例如:
fun.apply(this, ['eat', 'bananas'])
4.1.1 新的函数调用方式apply和call方法
-
function f1(x, y) {
-
console.log(
"结果是:" + (x + y) +
this);
-
return
"666";
-
}
-
f1(
10,
20);
// 函数的调用
-
-
console.log(
"========");
-
-
// apply和call方法也是函数的调用的方式
-
// 此时的f1实际上是当成对象来使用的,对象可以调用方法
-
// apply和call方法中如果没有传入参数,或者是传入的是null,那么调用该方法的函数对象中的this就是默认的window
-
f1.apply(
null, [
10,
20]);
-
f1.call(
null,
10,
20);
-
-
// apply和call都可以让函数或者方法来调用,传入参数和函数自己调用的写法不一样,但是效果是一样的
-
var result1 = f1.apply(
null, [
10,
20]);
-
var result2 = f1.call(
null,
10,
20);
-
console.log(result1);
-
console.log(result2);
4.1.2 apply和call可以改变this的指向
-
// 通过apply和call改变this的指向
-
function Person(name, sex) {
-
this.name = name;
-
this.sex = sex;
-
}
-
//通过原型添加方法
-
Person.prototype.sayHi =
function(x, y) {
-
console.log(
"您好啊:" +
this.name);
-
return x + y;
-
};
-
var per =
new Person(
"小三",
"男");
-
var r1 = per.sayHi(
10,
20);
-
-
console.log(
"==============");
-
-
function Student(name, age) {
-
this.name = name;
-
this.age = age;
-
}
-
var stu =
new Student(
"小舞",
18);
-
var r2 = per.sayHi.apply(stu, [
10,
20]);
-
var r3 = per.sayHi.call(stu,
10,
20);
-
-
console.log(r1);
-
console.log(r2);
-
console.log(r3);
4.2 call,apply使用
apply和call都可以改变this的指向。调用函数的时候,改变this的指向:
-
// 函数的调用,改变this的指向
-
function f1(x, y) {
-
console.log((x + y) +
":===>" +
this);
-
return
"函数的返回值";
-
}
-
//apply和call调用
-
var r1 = f1.apply(
null, [
1,
2]);
// 此时f1中的this是window
-
console.log(r1);
-
var r2 = f1.call(
null,
1,
2);
// 此时f1中的this是window
-
console.log(r2);
-
console.log(
"=============>");
-
//改变this的指向
-
var obj = {
-
sex:
"男"
-
};
-
// 本来f1函数是window对象的,但是传入obj之后,f1的this此时就是obj对象
-
var r3 = f1.apply(obj, [
1,
2]);
//此时f1中的this是obj
-
console.log(r3);
-
var r4 = f1.call(obj,
1,
2);
//此时f1中的this是obj
-
console.log(r4);
调用方法的时候,改变this的指向:
-
//方法改变this的指向
-
function Person(age) {
-
this.age = age;
-
}
-
Person.prototype.sayHi =
function(x, y) {
-
console.log((x + y) +
":====>" +
this.age);
//当前实例对象
-
};
-
-
function Student(age) {
-
this.age = age;
-
}
-
var per =
new Person(
10);
// Person实例对象
-
var stu =
new Student(
100);
// Student实例对象
-
// sayHi方法是per实例对象的
-
per.sayHi(
10,
20);
-
per.sayHi.apply(stu, [
10,
20]);
-
per.sayHi.call(stu,
10,
20);
总结
apply的使用语法:
1 函数名字.apply(对象,[参数1,参数2,...]);
2 方法名字.apply(对象,[参数1,参数2,...]);
call的使用语法
1 函数名字.call(对象,参数1,参数2,...);
2 方法名字.call(对象,参数1,参数2,...);
它们的作用都是改变this的指向,不同的地方是参数传递的方式不一样。
如果想使用别的对象的方法,并且希望这个方法是当前对象的,就可以使用apply或者是call方法改变this的指向。
4.3 bind
bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也可以接受预设的参数提供给原函数。一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
bind方法是复制的意思,本质是复制一个新函数,参数可以在复制的时候传进去,也可以在复制之后调用的时候传入进去。apply和call是调用的时候改变this指向,bind方法,是复制一份的时候,改变了this的指向。
语法:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
参数:
-
thisArg
- 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。
-
arg1, arg2, ...
- 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。
返回值:
返回由指定的this值和初始化参数改造的原函数的拷贝。
示例1:
-
function Person(name) {
-
this.name = name;
-
}
-
Person.prototype.play =
function() {
-
console.log(
this +
"====>" +
this.name);
-
};
-
-
function Student(name) {
-
this.name = name;
-
}
-
var per =
new Person(
"人");
-
var stu =
new Student(
"学生");
-
per.play();
-
// 复制了一个新的play方法
-
var ff = per.play.bind(stu);
-
ff();
示例2:
-
//通过对象,调用方法,产生随机数
-
function ShowRandom() {
-
//1-10的随机数
-
this.number =
parseInt(
Math.random() *
10 +
1);
-
}
-
//添加原型方法
-
ShowRandom.prototype.show =
function() {
-
//改变了定时器中的this的指向了
-
window.setTimeout(
function() {
-
//本来应该是window, 现在是实例对象了
-
//显示随机数
-
console.log(
this.number);
-
}.bind(
this),
1000);
-
};
-
//实例对象
-
var sr =
new ShowRandom();
-
//调用方法,输出随机数字
-
sr.show();
4.4 总结
-
call 和 apply 特性一样
- 都是用来调用函数,而且是立即调用
- 但是可以在调用函数的同时,通过第一个参数指定函数内部
this
的指向 - call 调用的时候,参数必须以参数列表的形式进行传递,也就是以逗号分隔的方式依次传递即可
- apply 调用的时候,参数必须是一个数组,然后在执行的时候,会将数组内部的元素一个一个拿出来,与形参一一对应进行传递
- 如果第一个参数指定了
null
或者undefined
则内部 this 指向 window
-
bind
- 可以用来指定内部 this 的指向,然后生成一个改变了 this 指向的新的函数
- 它和 call、apply 最大的区别是:bind 不会调用
- bind 支持传递参数,它的传参方式比较特殊,一共有两个位置可以传递
- 在 bind 的同时,以参数列表的形式进行传递
- 在调用的时候,以参数列表的形式进行传递
- 那到底以 bind 的时候传递的参数为准呢?还是以调用的时候传递的参数为准呢?
- 两者合并:bind 的时候传递的参数和调用的时候传递的参数会合并到一起,传递到函数内部。
5 函数的其它成员(了解)
- arguments
- 实参集合
- caller
- 函数的调用者
- length
- 函数定义的时候形参的个数
- name
- 函数的名字,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()
6 高阶函数
函数可以作为参数,也可以作为返回值。
6.1 作为参数
函数是可以作为参数使用,函数作为参数的时候,如果是命名函数,那么只传入命名函数的名字,没有括号。
-
function f1(fn) {
-
console.log(
"我是函数f1");
-
fn();
// fn是一个函数
-
}
-
-
//传入匿名函数
-
f1(
function() {
-
console.log(
"我是匿名函数");
-
});
-
// 传入命名函数
-
function f2() {
-
console.log(
"我是函数f2");
-
}
-
f1(f2);
作为参数排序案例:
-
var arr = [
1,
100,
20,
200,
40,
50,
120,
10];
-
//排序---函数作为参数使用,匿名函数作为sort方法的参数使用,此时的匿名函数中有两个参数,
-
arr.sort(
function(obj1, obj2) {
-
if (obj1 > obj2) {
-
return
-1;
-
}
else
if (obj1 == obj2) {
-
return
0;
-
}
else {
-
return
1;
-
}
-
});
-
console.log(arr);
6.2 作为返回值
-
function f1() {
-
console.log(
"函数f1");
-
return
function() {
-
console.log(
"我是函数,此时作为返回值使用");
-
}
-
-
}
-
-
var ff = f1();
-
ff();
作为返回值排序案例:
-
// 排序,每个文件都有名字,大小,时间,可以按照某个属性的值进行排序
-
// 三个文件,文件有名字,大小,创建时间
-
function File(name, size, time) {
-
this.name = name;
// 名字
-
this.size = size;
// 大小
-
this.time = time;
// 创建时间
-
}
-
var f1 =
new File(
"jack.avi",
"400M",
"1999-12-12");
-
var f2 =
new File(
"rose.avi",
"600M",
"2020-12-12");
-
var f3 =
new File(
"albert.avi",
"800M",
"2010-12-12");
-
var arr = [f1, f2, f3];
-
-
function fn(attr) {
-
// 函数作为返回值
-
return
function getSort(obj1, obj2) {
-
if (obj1[attr] > obj2[attr]) {
-
return
1;
-
}
else
if (obj1[attr] == obj2[attr]) {
-
return
0;
-
}
else {
-
return
-1;
-
}
-
}
-
}
-
console.log(
"按照名字排序:**********");
-
// 按照名字排序
-
var ff = fn(
"name");
-
// 函数作为参数
-
arr.sort(ff);
-
for (
var i =
0; i < arr.length; i++) {
-
console.log(arr[i].name +
"====>" + arr[i].size +
"===>" + arr[i].time);
-
}
-
-
console.log(
"按照大小排序:**********");
-
// 按照大小排序
-
var ff = fn(
"size");
-
// 函数作为参数
-
arr.sort(ff);
-
for (
var i =
0; i < arr.length; i++) {
-
console.log(arr[i].name +
"====>" + arr[i].size +
"===>" + arr[i].time);
-
}
-
-
console.log(
"按照创建时间排序:**********");
-
// 按照创建时间排序
-
var ff = fn(
"time");
-
// 函数作为参数
-
arr.sort(ff);
-
for (
var i =
0; i < arr.length; i++) {
-
console.log(arr[i].name +
"====>" + arr[i].size +
"===>" + arr[i].time);
-
}
7 总结
函数中的this的指向:
- 普通的函数中this是window
- 构造函数中的this是实例对象
- 方法中的this是实例对象
- 原型中的方法中的this是实例对象
- 定时器中的this是window
函数是对象,对象不一定是函数,对象中有__proto__ 函数中有prototype。apply,call,bind方法是用来改变this指向的。apply和call是调用方法的时候改变this指向,bind方法是复制一个新的函数,改变this的指向。函数可以作为参数使用,也可以作为返回值使用。函数作为参数使用的时候,这个函数可以是匿名函数,也可以是命名函数。
今天的学习就到这里了,你可以使用今天学习的技巧来改善一下你曾经的代码,如果想继续提高,欢迎关注我,每天学习进步一点点,就是领先的开始。如果觉得本文对你有帮助的话,欢迎转发,评论,点赞!!!
转载:https://blog.csdn.net/qq_23853743/article/details/108435874