1. 创建函数
  2. 调用函数
  3. 匿名函数
  4. 自执行匿名函数(IIFE)
  5. 箭头函数
  6. return
  7. arguments
  8. 构造函数
  9. 作用域
    1. 全局变量
    2. 局部变量
    3. 变量/函数提升
  10. 闭包
  11. this
  12. 修复this指向
  13. 参考

JavaScipt function

函数也是对象,所有函数都是Function的实例.

1
2
a.constructor === Function // true
Function.__proto__

创建函数

有两种方法创建函数,函数声明和函数表达式

  • 函数声明
1
2
3
4
function say(word) {
console.log(word);
}
say('hi');
  • 函数表达式
1
2
3
4
var say = function(word) {
console.log(word);
}
say('hi');

调用函数

用函数名后接一对括号的形式调用函数,括号内填函数所需参数

1
say('hi') //hi

也可以把函数赋值给一个变量,通过变量调用函数.但这样就不能在外部使用原函数名调用了.

1
2
3
4
5
6
7
8
9
10
11
var b = function say() {
console.log('hi');
}
b(); // hi
say(); // ReferenceError: say is not defined
//而在函数内部是能正常调用的
var b = function say() {
console.log('hi');
say();
}
b(); //hi, hi, hi ......

匿名函数

函数名可以省略,这种结构被称为匿名函数

1
2
3
function(word) {
console.log('hi');
}

可以将匿名函数赋值给一个变量,通过这个变量调用匿名函数.

1
2
3
4
var say = function(word){
console.log(word)
};
say('hi');

匿名函数常用于事件绑定,闭包储存,立刻执行函数等等

//打印触发事件的元素名
window.onclick = function(event){
    alert(event.target.tagName);
} 

//声明后立即执行 
(function(){console.log('hi')})() //hi

自执行匿名函数(IIFE)

声明匿名函数后,立即执行.需用一对括号包裹,否则会报错

1
2
3
4
5
6
(function(){
console.log('hi');
})() // hi
function(){
console.log('hi')
}() //SyntaxError: Unexpected token (

也有在函数前加叹号的写法(不建议使用)
因为这两个符号会对返回结果产生影响.如果不需要处理返回结果,可以使用这种方法

1
2
3
4
5
!function(){console.log('hi')}()
~function(){console.log('hi')}()

!function(){return true}(); // false
~function(){return true}(); // -2

箭头函数

箭头函数是es6新增函数语法,解决了this的指向问题

1
2
3
var say = (word) => {
console.log(word);
}

如果函数只有一行,可省略大括号

1
var say = (word) => console.log(word);

如果函数只有一个参数,还可省略参数外的括号

1
var say = word => console.log(word);

箭头函数没有自己的this,而是使用创建环境上下文的this(在哪创建的就用哪的)

1
2
3
4
5
function cat(){
this.name ='anna';
setTimeout(()=>console.log(this.name), 100); // anna
}
new cat();

return

函数执行时遇到return语句,则停止运行,并返回指定数据

1
2
3
4
function t() {
return 'hi';
}
t() // hi

如果函数中没有return语句/只有return,则返回undefined.

1
2
3
4
5
function t() {
return;
alert('hi');
}
t() // undefined

arguments

arguments是函数的特殊属性,包含调用函数时传入的所有变量

1
2
3
4
5
6
function print(a, b) {
for (var i = 0;i < arguments.length; i++){
console.log(arguments[i])
}
}
print(1, 2) //1 2

未定义的参数,也能用arguments获取到

1
print(1, 2, 3) // 1 2 3

修改arguments项,参数也会随之发生改变,反之亦然.

1
2
3
4
5
6
7
8
9
10
function test(a){
console.log(arguments[0]); // 1
// 修改变量a,arguments随之变化
a = 2;
console.log(arguments[0]); // 2
// 修改arguments[0],变量a随之变化
arguments[0] = 3;
console.log(a); // 3
}
test(1);

虽然arguments是类数组对象,无法对其使用push pop等方法.

1
2
3
4
function print() {
arguments.push(1);
}
print(); // TypeError: arguments.push is not a function

可用Array.prototype.slice或Array.from把arguments转换成的数组

1
2
3
4
5
6
function print() {
var arg = Array.from(arguments);
arg.push(1);
console.log(arg);
}
print(); // [1]

构造函数

构造函数就是普通函数, 使用new关键字调用,返回一个对象
调用构造函数时,会以如下方式运行:

  • 新建对象
  • 将构造函数的作用域赋给新建对象
  • 运行构造函数内的代码
  • 返回新建对象
1
2
3
4
5
6
7
8
function Cat(name){
this.name = name;
this.say = function(word){
console.log(word);
}
}
c = new Cat('anna'); // Cat {name: "anna", say: ƒ}
c.say('hi'); // hi

未绑定在this上的属性不会被反回

1
2
3
4
5
function Cat(name){
name = name;
}
c = new Cat('anna');
c.name; // undefined

作用域

js中的变量分为两种,局部变量和全局变量.

全局变量

在函数外声明的变量叫全局变量,所有全局变量都被绑定在window下

var v = 1;
console.log(window.v) // 1

在函数中,不使用var关键字声明的变量也是全局变量

(function(){
    v = 1;
})();
//v是一个全局变量,因此可以在函数外被获取
console.log(v); //1
console.log(window.v); //1

局部变量

在函数中,用var关键字声明的变量叫"局部变量"
不能在函数外读取函数内的局部变量,但能在函数内读取函数外的变量
如果内部局部变量名和外部变量名相同,内部的局部变量会覆盖外部的变量

var a = 1;
(function(){
    var b = 2;
    console.log(a); // 1
})()
//b是局部变量,在函数外无法获得.
console.log(b); //ReferenceError: b is not defined

注:函数的参数以及arguments等内部关键字也是局部变量

变量/函数提升

js在编译时,会把所有变量和函数提升到作用域顶部

(function test() {
    a = 1; 
    console.log(window.a); //undefined
    var a = 2;
    console.log(a); // 2
})();

//实际执行顺序
 (function test() {
    var a;     
    a = 1; //var a 被提升到顶部,这里的a = 1实际上是局部变量
    console.log(window.a); //undefined
    var a = 2;
    console.log(a); // 2
})();   

函数也会被提升,即便先调用后声明函数.也能正常执行

say(); // hi
function say() {
    console.log('hi');
}

//实际的执行顺序
function say() {
    console.log('hi');
}
say(); // hi 

需注意的是,变量只会被提升,不会立刻赋值.

say(); // TypeError: say is not a function
var say = function(){console.log('hi')};

//实际执行顺序
var say; //虽然变量say被提升到作用域顶端,但右边的匿名函数还没被赋值给say.
say(); 
say = function(){console.log('hi')}; //运行到这里才被赋值

闭包

什么是闭包?如果一个"函数"能访问作用域中的"变量",则称函数和变量为闭包.
所有Js函数都是闭包,因为他们都能访问全局作用域中的变量

1
2
3
4
5
6
7
function a(){
// 函数b能访问a中的变量
var temp = 1;
function b(){
console.lot(b);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var arr = [];
for (var i = 0; i <= 10; i++) {
arr.push(function(){
console.log(i);
})
}
arr[0]() // 11

var arr = [];
for (var i = 0; i <= 10; i++) {
arr.push( (function(i){
return function(){console.log(i)};
})(i) );
}
arr[0]() // 0

this

this是函数内部的特殊关键字,指向函数的调用位置.

1 在全局范围调用函数,this会指向全局对象(window).

var word = 'hi';
function say(){
    console.log(this.word);
}
say(); //hi

严格模式下,函数内的this将不再被封装成对象.

"use strict";
var word = 'hi';
function say(){
    console.log(word); // hi
    console.log(this.word); 
    // TypeError: Cannot read property 't' of undefined
}
say(); //hi  

2 在对象的方法中调用,指向该对象

var cat = {
    name:'anna',
    //this指向cat对象
    getName: function(){
        return this.name;
    }
}
cat.getName(); // anna

3 在构造函数中调用,指向新生成的对象

var Cat = function() {
    this.name = "anna";
}
Cat.prototype.getName = function(){
    return this.name;
}

var cat = new Cat();
cat.name = 'dog';
//虽然新创建对象的prototype上指向原构造函数的prototype,但其中的this仍指向新生成对象.
cat.getName(); // dog

注意,把方法从对象中拿出来调用,也会造成this的指向改变

function Cat() {
    this.name = 'anna';
    this.getname = function(){
        return this.name;
    }   
}
var c = new Cat();
console.log(c.getname()); // anna
var c = this.getname;
//c中的this指向window
console.log(c()); // c is not a function

4 使用call apply bind方法调用
call apply bind都可以改变函数的上下文,使函数中的this指向传入的第一个参数
function.call(thisArg,arg1,arg2,...)
function.apply(thisArg,[arg1,arg2,...])
function.bind(thisArg,arg1,arg2,...)

call apply bind的区别
1 call和apply会立刻执行函数,而bind会返回一个函数

var cat = {
    name: 'anna',
}
function printName() {
    console.log(this.name);
}
//把printName的上下文指向cat
printName.call(cat); // anna
printName.apply(cat); // anna
//bind返回一个函数
printName.bind(cat); // ƒ printName()
printName.bind(cat)(); // anna 

2 call和apply的区别是接收参数格式不同
call需要把函数参数依序传入,而apply接受一个数组,内含所有函数参数

var num = {
    c: 3
}
function sum(a, b){
    console.log(a + b + this.c);
}

sum.call(num, 1, 2); //6
sum.apply(num, [1, 2]); //6
//bind的参数格式和call一样
sum.bind(num, 1, 2)(); //6

需注意,对同一个函数多次调用bind方法,只有第一次有效
这是因为bind函数会创建一个新函数(绑定函数),内部包含原函数和call方法,用call把原函数其他对象

var num = {n: 1}
var num1 = {n: 10}

function printN(){
    console.log(this.n);
}
printN.bind(num).bind(num1)(); //1    

使用new调用构造函数时,bind方法无效

修复this指向

setTimeout和setInterval这类函数中的this指向window,因为在运行的时候上下文环境已经变了

function Person() {
    this.age = 0;
    console.log(this.age++); // 0
    setInterval(function growUp() {
        console.log(this.age++); //NaN
    }, 1000);
}
new Person();

可以用以下几种方法修复setTimeout和setInterval的指向问题

0 使用箭头函数

function Person() {
    this.age = 0;
    console.log(this.age++); // 0
    setInterval(() => {
        console.log(this.age++); //NaN
    }, 1000);
}
new Person();

1 储存this到其他变量

function Person() {
    this.age = 0;
    //把this保存到that中
    var that = this; 
    setInterval(function() {
        console.log(that.age++);
    }, 1000);
}
new Person();

2 使用闭包保存this

function Person() {
    this.age = 0;
    setInterval(
        // 将this保存在自执行匿名函数中,返回函数upage.
        // 函数upage在匿名函数中,可以读取匿名函数中的局部变量.
        // 因为upage依赖匿名函数.匿名函数在被执行后,不会被立刻销毁.
        // 这样做可以避免不必要的变量污染局部作用域.
        (function(self) {
            return function upage() {
                console.log(self.age++);
            }
        })(this),
    1000);
}
new Person();

3 使用bind绑定this

function Person() {
    this.age = 0;
    var callback = function() {
        console.log(this.age++);
    }
    //setInterval接受一个函数作为参数,稍后执行.
    // 因此这里要用bind,而不是call|apply
    setInterval(callback.bind(this), 1000);
}
new Person();

参考

JavaScript 秘密花园
Javascript的this用法 阮一峰
严格模式 MDN
bind MDN
学习Javascript闭包(Closure)阮一峰
箭头函数 MDN