你真的了解JS的函数吗?

263 阅读5分钟

v2-5dafb089be4b11133bb3fcd198bbbb50_720w.jpg

1.函数声明和函数表达式

我们先来看看常见形式

  • 函数声明: function 函数名称 (参数:可选){ 函数体 }
  • 函数表达式: function 函数名称(可选)(参数:可选){ 函数体 }

如果function foo(){}是作为赋值表达式的一部分的话,那它就是一个函数表达式

如果function foo(){}被包含在一个函数体内,或者位于程序的最顶部的话,那它就是一个函数声明。

比如

// 声明,因为它是程序的一部分
function foo(){} 

 //表达式,因为它是赋值表达式的一部分
var bar = function foo(){};

// 表达式,因为它是new表达式
new function bar(){}; 

(function(){
// 声明,因为它是函数体的一部分
function bar(){} 
})();

还有一种函数表达式不太常见,就是被括号括住的(function foo(){}),他是表达式的原因是因为括号 ()是一个分组操作符,它的内部只能包含表达式

差别
  • 函数声明存在着函数提升,函数表达式不会

2.立即执行函数

下面是我们常见的两种写法,因为JavaScript里括弧()里面不能包含语句,所以,解析器在解析function关键字的时候,会将相应的代码解析成function表达式,而不是function声明。

(function(){ 
    var a; 
    //code
}());

(function(){
    var a; 
    //code
})();

下面这种写法则不会被解析成立即执行函数

function(){
    var a; 
    //code
}(1);

它等价于,一个函数表达式和一个常量表达式

function(){
    var a; 
    //code
}

(1)

但是,括号有个缺点,那就是如果上一行代码不写分号,括号会被解释为上一行代码最末的函数调用,产生完全不符合预期,并且难以调试的行为,加号等运算符也有类似的问题。

所以一些推荐不加分号的代码风格规范,会要求在括号前面加上分号。

立即执行函数的另一种写法
void function () {
	var a = 100;
	console.log(a);

}();

语义上 void 运算表示忽略后面表达式的值,变成 undefined

3.this的指向

通常情况下

function showThis(){
    console.log(this);
}

var o = {
    showThis: showThis
}

showThis(); // global
o.showThis(); // o

调用函数时使用的引用,决定了函数执行时刻的 this 值。所以这里全局调用函数this指向global,使用对象调用函数,this指向对象

在strict模式下

"use strict"
function showThis(){
    console.log(this);
}

var o = {
    showThis: showThis
}

showThis(); // undefined
o.showThis(); // o

当严格模式时使用,this 严格按照调用时传入的值,可能为 null 或者 undefined。

箭头函数

const showThis = () => {
    console.log(this);
}

var o = {
    showThis: showThis
}

showThis(); // global
o.showThis(); // global

箭头函数的this只与定义时的作用域有关。

类的方法

class C {
    showThis() {
        console.log(this);
    }
}
var o = new C();
var showThis = o.showThis;

showThis(); // undefined
o.showThis(); // o

因为 class 设计成了默认按 strict 模式执行。

4.JavaScript 中,调用函数有哪几种方式?

  • 方法调用模式 Foo.foo(arg1, arg2);
  • 函数调用模式 foo(arg1, arg2);
  • 构造器调用模式 (new Foo())(arg1, arg2);
  • call/apply 调用模式 Foo.foo.call(that, arg1, arg2);
  • bind 调用模式 Foo.foo.bind(that)(arg1, arg2)();

1,call()

函数在执行的时候,实际上默认调用了call方法

test()  //   等价于test.call()

这是默认调用call方法。。但是当我给他加上参数。。它就完成一些很强大的功能。 函数调用call方法默认可以改变函数内部的this指向。 它的第一个参数,是this改变后指向的对象,后面的参数对应函数执行的参数。 举个例子:

//现在的this默认指向调用者
function Person(name){
	this.name=name;
}
//我们创建一个空对象
var person={};
//那么我们想让Person中的this指向person怎么办
//这样
Person.call(person,'jackson');
//最后打印出person
person={
	name:'jackson'
}

我们可以看到,调用了call后,实际上在Person执行的时候,this.name就变成了person.name。

2,apply()

apply的用法和call很像,它的第一个参数依旧是改变函数执行的时候的this指向,不同的是,函数执行的时候的各个形参,需要被放在一个数组里面,做为执行时候的第二个参数。

function Person(name,age){
	this.name=name;
	this.age=age;
}
var person={}
//第二个参数为数组
Person.apply(person,['Osborn',22])

3,bind()

bind的用法和前面两个都有所不同,它有延迟执行的特点,它返回一个新的函数。 bind()的第一个参数代表函数执行的this的指向,后面的参数可以用来执行函数执行时候的形参。

this.num = 9; 
var mymodule = {
  num: 81,
  getNum: function() { 
    console.log(this.num);
  }
};

mymodule.getNum(); // 81

var getNum = mymodule.getNum;
getNum(); // 9, 因为在这个例子中,"this"指向全局对象

var boundGetNum = getNum.bind(mymodule);
boundGetNum(); // 81

它还有一个很好的作用,就是预先指定参数, 只要将这些参数(如果有的话)作为bind()的参数写在this后面。当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们后面。

function list() {
	//这里相当于将函数参数放到一个数组中返回
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

// 预定义参数37
var leadingThirtysevenList = list.bind(undefined, 37);

var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
如果对一个函数进行多次 bind,那么上下文会是什么呢?
let a = {};
let fn = function () {
  console.log(this);
};
fn.bind().bind(a)(); // => ?

不管我们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定,所以结果永远是 window。

5.如何实现一个 new?

function _new(func, ...args) {
  let obj = Object.create(func.prototype); // 原型
  let res = func.apply(obj, args); // 初始化对象属性
  return res instanceof Object ? res : obj; // 返回值
}