目录
函数的介绍
定义:函数实际上是对象,每个函数都是Function类型的实例。
函数的作用:
- 功能的封装,直接调用,代码复用率高
- 构建对象的模板(构造函数)
函数声明
1.函数声明
function 函数名(形参列表){
//函数体
}
2.函数表达式
var 函数名=function(形参列表){
//函数体
}
3.函数声明提前
函数会提升到当前作用域的最顶部,优先级高于var.
// 函数也是有变量声明提前的
function add(a,b){
var result=a+b;
return result;
}
var total=add(1,2);
foo(); //函数声明提升到代码的最前面,可以直接调用函数
function foo(){
console.log('你好呀'); //你好呀
// return;
// console.log('2');
// return之后的语句不执行
// 如果没有返回的内容,则在写代码的时候不关注返回值
// 没有return:代码执行到大括号
}
// 变量声明提前
console.log(a); //undefined
var a='你好呀';
console.log(a); //你好呀
函数的内部属性
只有在函数内部才能访问的属性,this也可以在函数外部进行。
1.arguments
argumrnts是一个类数组对象,里面包含着传入函数的所有参数,该对象有一个名为callee的指针,指向拥有这个arguments对象的函数。
function foo(a,b){
//当传递的实参个数超过形参的个数的时候,不会报错,所有的实参都会
// 保存在arguments里 arguments类数组
console.log(arguments[3]);//6
console.log(a,b);//1,2
// arguments.callee 函数名
// return foo(n-1)*n 等价于 arguments.callee(n-1)*n
}
foo(1,2,5,6,7,8);
// foo.length获取一个函数形参的长度
console.log(foo.length); //2
callee 属性是 arguments 对象的一个成员,仅当相关函数正在执行时才可用。
callee 属性的初始值就是正被执行的 Function 对象。
如何将类数组转换为数组
function test(a, b, c) {
console.log(arguments.length); //5
// 如何将类数组对象转换为数组 3种方法
// 1.Array.from();
console.log(Array.from(arguments)); //[1,2,3,4,5]
// 2.Array.prototype.slice.call(arguments, 0)
console.log(Array.prototype.slice.call(arguments, 0)); //[1,2,3,4,5]
// 3....拓展运算符
let arr = [...arguments];
console.log(arr);//[1,2,3,4,5]
}
test(1,2,3,4,5);
2.this
- 在方法中,this表示该方法所属的对象。
- 如果单独使用,this表示全局对象。
- 在函数中,this表示全局对象。
- 在事件中,this表示接受事件的元素。
- 在显式函数绑定时,我们可以自己决定this的指向。
在方法中的this
// 谁调用了这个方法 this就指向谁
// 在方法中,this表示该方法所属的对象
var person={
name:'lisi',
sayName:function(){
console.log(this.name); //lisi
}
}
person.sayName();
单独使用this
单独使用this,则它指向全局对象。
1. 在浏览器中,window就是该全局对象。
<script>
console.log(this); //window
</script>
2. 在node中,指向的是一个{}。
var x=this;
console.log(x); //{}
函数中使用this(默认)
在函数中,函数的所属者默认绑定在this上。
1. 在浏览器中,window就是该全局对象。
<script>
function foo() {
console.log(this); //window
}
foo();
</script>
2. 在node中,指向的就是global对象。
function foo() {
console.log(this); //global
}
foo();
事件中的this
this指向了接收事件的HTML元素:
<button onclick="this.style.display='none'"> 点我后我就消失了 </button>
关于this案例
// 案例一
var o = {
a: 10,
b: {
a: 12,
fn: function () {
console.log(this.a); //12
console.log(this); //{ a: 12, fn: [Function: fn] }
// b对象调用了fn(),所有this指向b对象
}
}
}
o.b.fn();
// 案例二
var o = {
a: 10,
b: {
fn: function () {
console.log(this.a); //undefined
// b对象调用fn(),fn里面没有a,所有返回undefined
console.log(this);//{ fn: [Function: fn] }
}
}
}
o.b.fn();
// 案例三
var point = {
x: 0,
y: 0,
moveTo: function (x, y) {
var moveX = function (x) {
// console.log(this); //global
// 因为MoveX(x)前面没有实例对象调用,所有this指向全局变量
this.x = x;
};
var moveY = function (y) {
this.y = y;
}
moveX(x);
moveY(y);
}
};
point.moveTo(1, 1);
console.log(point.x);//0
console.log(point.y);//0
console.log(x);//1
console.log(y);//1
// 案例四
var point = {
x: 0,
y: 0,
moveTo: function (x, y) {
var that = this;
console.log(this); //{ x: 0, y: 0, moveTo: [Function: moveTo] }
// 内部函数
var moveX = function (x) {
that.x = x;
// this.x = x;
};
// 内部函数
var moveY = function (y) {
that.y = y;
// this.y = y;
}
moveX(x);
moveY(y);
}
};
point.moveTo(1, 1);
console.log(point.x); //1
console.log(point.y); //1
console.log(x);// 报错
console.log(y);//报错
显示函数绑定
apply和call可以决定this的指向。
var person1={
sayName:function(a){
console.log(this.name+a); //lisi123
}
}
var person2={
name:'lisi'
}
//person2作为参数调用person1.sayName方法时,this将指向person2,即它是person1的方法
person1.sayName.call(person2,123);
IIFE
IIFE: Immediately Invoked Function Expression,意为立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数。
作用
- 页面加载完成后只执行一次的设置函数。
- 将设置函数中的变量包裹在局部作用域中,不会泄露成全局变量。
写法
//第一种
(function(形参){
函数体内容
})(实参);
//第二种
(function(形参){
函数体内容
}(实参));
function foo() {
console.log(123);
}
foo();
// 如果后面跟的是立即执行函数,前面的结束语句必须要加分号
// 立即执行函数
(function (a) {
console.log(456 + a); //579
})(123)
// 返回的是一个布尔值,然后进行取反
var res = !function () {
return '123'; //false
// return ''; //true
}()
console.log(res);
//对于数字返回的是原来的结果,非数字返回NaN
var res = +function (a) {
return 'null'; //NaN
// return 123; //123
}('456')
console.log(res);
// 对于数字返回的是正负符号相反,非数字返回NaN
var res = -function (a) {
return 'null'; //NaN
// return 123; //-123
}('456')
console.log(res);
// 对于数字返回的是正负符号相反在减1,非数字返回-1
var res = ~function (a) {
// return 'null'; //-1
return 123; //-124
}('456')
console.log(res);
//返回的结果是undefined
var res = function () {
}()
console.log(res); //undfined
案例
for (var i = 0; i < 5; i++) {
function a() {
console.log(i); //5
//因为 i 是全局作用域,当循环结束后 i 的值是5
}
}
a();
for (var i = 0; i < 6; i++) {
(function (i) {
console.log(i); //0,1,2,3,4,5
// js中调用函数传递参数都是值传递,所有当立即执行函数时,
// 首先会把参数i的值赋值一份,然后再创建函数作用域来执行
// 函数,循环5次就会创建5个作用域,所有每个输出访问的都是
// 不同作用域的i 的值。
})(i)
}
作用域
函数作用域:函数中声明的变量,会成为函数的局部变量。
全局作用域:函数外声明的变量,会成为全局变量。
// es5中没有块级作用域
{
var a=10;
}
console.log(a); //10
var a = 10;
function foo() {
//当函数内部有变量a时,会产生局部作用域,外部全局作用域中的a不会对函数内部造成影响
//如果把函数内部的变量a注释掉时,函数内部的a输出的就是全局作用域中的a
console.log(a); //undefined
var a = 100;
console.log(a); //100
function fn() {
console.log(a); //undefiined
var a = 200;
console.log(a); //100
}
fn()
}
foo()
作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
作用域链
自由变量:当前作用域没有定义的变量,就是自由变量。想要得到自由变量,要到创建这个函数的父级作用域寻找,没有就一直向上级祖先元素寻找。
var a = 100
function fn() {
var b = 200
console.log(a) // 这里的a在这里就是一个自由变量 // 100
console.log(b)
}
fn()
案例
var a=10;
function fn(){
var b=10;
function bar(){
console.log(a+b); //20;
//父级作用域中有两个b可以选择,创建这个函数的父级作用域优先
}
return bar;
}
var x=fn(), //执行fn() 返回的是bar
b=200;
x(); //执行x,就是执行bar函数
函数调用
函数声明好之后并不会直接运行,需要进行调用才能进行。
- 函数名(实参列表);
(修改this指向问题,怎么修改执行环境上下文对象/this)
- 函数名.call(执行环境对象,实参列表)
- 函数名,apply(执行环境对象,实参列表数组)
- 函数名.bind(执行环境对象)(实参列表) //第一次调用返回的是修改后的函数
var obj = {
name: 'zhangsan',
sayName: function () {
console.log(this.name); //undefined,指向global
}
}
var b = obj.sayName;
console.log(b); //[Function;sayName]
b(); //执行sayName
obj.sayName(); //zhangsan
以下案例就是让this指向obj。
1. call(执行环境对象,实参列表)
调用call方法,this就会执行环境对象。
var obj = {
name: 'zhangsan',
sayName: function () {
console.log(this.name); //zhangsan
}
}
var b = obj.sayName;
b.call(obj);
还可以传递多个实参
var obj = {
name: 'zhangsan',
sayName: function (a, b) {
console.log(this.name); //zhangsan
console.log(a, b); //5 你好
}
}
var b = obj.sayName;
b.call(obj, 5, '你好');
2. apply(执行环境对象,实参列表数组)
apply和call方法比较相似,也可以改变this的指向。
var obj = {
name: 'zhangsan',
sayName: function () {
console.log(this.name); //zhangsan
}
}
var b = obj.sayName;
b.apply(obj);
apply可以有多个参数,但第二个参数必须是数组
var obj = {
name: 'zhangsan',
sayName: function (a,b) {
console.log(this.name); //zhangsan
console.log(a,b); //10,20
}
}
var b = obj.sayName;
b.apply(obj, [10,20]);
注意:如果call和apply的第一个参数是null,那么this在node环境下指向的是global对象,在HTML中指向的是window对象。
var obj = {
name: 'zhangsan',
sayName: function () {
console.log(this); //global
}
}
var b = obj.sayName;
b.apply(null);
3. bind(执行环境对象)(实参列表)
var obj = {
name: 'zhangsan',
sayName: function () {
console.log(this.name); //zhangsan
}
}
var b = obj.sayName;
b.bind(obj);
// 代码没有被打印,这就是bind和call、apply方法的不同,实际上bind返回的是一个修改过后的函数
// 新建一个变量c来接收bind修改后的函数
var c = b.bind(obj);
console.log(c); //[Function: bound sayName]
// 执行c
c();
bind也有多个参数,并且参数可以执行的时候再次添加,但要注意的是,参数是按照形参的顺序进行的。
var obj = {
name: 'zhangsan',
sayName: function (a, b) {
console.log(this.name); //zhangsan
console.log(a, b); //1 你好
}
}
var b = obj.sayName;
b.bind(obj);
// 代码没有被打印,这就是bind和call、apply方法的不同,实际上bind返回的是一个修改过后的函数
// 新建一个变量c来接收bind修改后的函数
var c = b.bind(obj, 1, '你好');
console.log(c); //[Function: bound sayName]
// 执行c
c();
总结:call和apply都是改变上下文中的this并立即执行这个函数,bind方法可以让对应的函数想什么时候调用就什么时候调用,并且可以将参数在执行的时候添加,这是它们的区别,根据自己的实际情况来选择使用。
回调函数
主函数的事先做完,回头再调用传进来的那个函数(函数可以作为参数)。
// 定义主函数,回调函数作为参数
function A(B) {
B();
console.log('我是函数A');
}
// 定义回调函数
function B() {
// 模拟异步操作 -> 延时操作
setTimeout(() => {
console.log('我是函数B');
}, 2000);
}
// 调用主函数,将函数B传进去
A(B)
回调函数的作用:回调函数一般都在用在耗时的操作上,因为主函数不用等待回调函数执行完,可以接着执行自己的代码。(函数也可作为返回值)
闭包
闭包(一种特殊的对象)就是指有权访问另一个函数作用域中的变量的函数。
闭包参数的三个必要条件
- 函数嵌套函数
- 内部函数引用了外部函数中的数据(属性、函数)
- 参数和变量不会被回收
待定条件:一个函数return另一个函数。
最常见的闭包方式
function fun() {
var a = 1, b = 2;
function A() {
return a + b;
}
return A;
}
console.log(fun()()); //3
变量始终保持在内存中。
function f1() {
var n = 999;
nAdd = function () {
n += 1;
}
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); //999
nAdd();
result();//1000
f1是f2的父函数,而f2(f2实际上就是result)被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var name = 'window-name'
var obj = {
name: 'obj-name',
say: function () {
return function () {
console.log(this.name); //window-name
}
}
}
var x = obj.say()
console.log(x); //ƒ () { console.log(this.name);}
x()
console.log(this); //window
</script>
</body>
</html>