JS基础-5|原生JS函数、arguments、call、apply、bind、闭包、构造函数,ES6箭头函数等

594 阅读6分钟

函数是业务开发中最常见的。本篇一起讨论一下函数的变化。

原生js的函数

定义函数

如何定义函数,想必不用多说,如下2种方式:

function sum(a, b) {
  return a + b;
}
sum(1, 2); // 3

var sum = function (a, b) {
  return a + b;
}
sum(1, 2); // 3

函数调用

上述代码中已经提及一种调用方式,也是最常见的调用方式。

函数调用总共3种方式:

  • 当事件发生时(当用户点击按钮时,函数绑定到dom上)
  • 当 JavaScript 代码调用时(上述代码的方式)
  • 自动的(自调用或自执行)
(function(a, b){
    return a + b;
})(1, 2) // 3

arguments

arguments是JavaScript函数内置对象。它包含函数调用时使用的参数数组

function sum () {
  var result = 0;
  for (var i = 0; i < arguments.length; i++) {
    result += arguments[i]
  }
  return result;
}
sum(1, 2); // 3
sum(1, 2, 3); // 6

使用arguments后参数数量就可以不做限制了。

函数参数

关于参数注意2点:

  • 基础类型的参数通过值传递
  • 对象类型的参数通过引用传递的
function test (data) { 
  data = 10;
}; 
var a = 1; 
test(a); 
console.log(a); // 1 

function test (data) { 
  data.key = 10;
}; 
var a = { key: 1 }; 
test(a); 
console.log(a); // { key: 10 }

this

原生JS的this在之前是必考问题,现在也会问到。

在js中this的指向是函数所有者,全局函数this指向window对象,函数属于谁,this就指向谁,谁都不属于指向widnow

// html
<button onclick="test">测试<button>

// javascript
function test(){
  console.log(this); // DOM对象
}

上一篇对象中关于super关键字的分析,它和this的区别:

  • super指向当前对象的原型对象
  • this指向的是函数所有者
  • super只能用在对象的方法之中,用在其他地方都会报错。

call、apply、bind

call、apply、bind这三个算是JS的重点之一。

call

call可以用来调用其他对象的方法。

官方例子:

var person = {
  fullName: function(city, country) {
    return this.firstName + " " + this.lastName + "," + city + "," + country;
  }
}
var person1 = {
  firstName:"Bill",
  lastName: "Gates"
}
person.fullName.call(person1, "Seattle", "USA");

使用call方法调用person的fullName方法,注意此时this指向为call的第一个参数

apply

apply方法与call方法相似,区别在apply的参数是数组方式

官方例子:

var person = {
  fullName: function(city, country) {
    return this.firstName + " " + this.lastName + "," + city + "," + country;
  }
}
var person1 = {
  firstName:"John",
  lastName: "Doe"
}
person.fullName.apply(person1, ["Oslo", "Norway"]);

使用apply方法调用person的fullName方法,注意此时this指向为call的第一个参数

bind

关于bind的在W3C中并未找到,请查阅MDN的Function.prototype.bind()

bind方法和call方法类似,传参也一致,但是bind方法不会立即执行,需手动调用后才执行

var person = {
  fullName: function(city, country) {
    return this.firstName + " " + this.lastName + "," + city + "," + country;
  }
}
var person1 = {
  firstName:"John",
  lastName: "Doe"
}
person.fullName.bind(person1, ["Oslo", "Norway"])();

闭包

闭包大家都不陌生,在面试中经常会被提及的知识点。在W3C的JavaScript 闭包中由浅入深介绍了闭包,对闭包不熟悉的可以学习一下。

先看一下官方的例子

var add = (function () {
    var counter = 0;
    return function () {return counter += 1;}
})();

add();
add();
add(); // 3

看完代码聊一下闭包的特点:

  1. 函数嵌套函数;
  2. 内部函数使用外部函数的参数和变量;
  3. 参数和变量不会被垃圾回收机制回收。

基于闭包的特点和代码分析,可以总结如下优点:

  1. 变量始终保持在内存中不会被销毁。
  2. 由于局部变量,防止污染全局变量

有优点也有缺点:

  1. 常驻内存,增加内存使用量。
  2. 使用不当会很容易造成内存泄露。

构造函数(对象构造器函数)

构造函数都不陌生,是创建对象时初始化对象,并且总与new运算符一起使用。

比如Object()Array()等构造函数。除了内置的构造函数外,js允许自定义构造函数。

function Person() {
  this.age = 18;
  this.say = function () {
    console.log("I'm " + this.name + ', my age is ' + this.age)
  }
  this.__proto__.hello = function () {
    console.log("I'm " + this.name + ', my age is ' + this.age)
  }
}
var man = new Person();
man.name = '张三';
man.say(); // I'm 张三, my age is 18
console.log(man); // Person{}

上述例子是个简单的构造函数,其中有sayhello方法,say是显式添加方法,hello是原型方法。

总结一下构造函数和普通函数的区别:

  • 命名规范:构造函数首字母需大写。
  • 调用方式:构造函数通过new 函数名()调用
  • 返回值: 构造函数返回对象,普通函数需要return。

类型检测

ObjectArray等都能检测类型,那么自定义的构造函数如何进行类型检测呢?

利用前几章的知识来试验一下哪个方法可以成功

// typeof
console.log(typeof man); // object

// instanceof
console.log(man instanceof Person); // true
console.log(man instanceof Object); // true

// constructor
console.log(man.constructor === Person); // true
console.log(man.constructor === Object); // false

// getPrototypeOf
console.log(Object.getPrototypeOf(man) === Person.prototype); // true
console.log(Object.getPrototypeOf(man) === Object.prototype); // false

通过以上试验,constructor和getPrototypeOf可以检测出是否基于构造函数创建

ES6新增特性

参数:默认值、rest参数

原生js函数的参数不能设置默认值,只能采用变通的方法。ES6支持设置默认值,减少了代码复杂度。

// JS
function sumByJs(a, b) {
  if (typeof a === 'undefined') a = 0;
  if (typeof b === 'undefined') b = 0;
  return a + b;
}
console.log(sumByJs()); // 0

// ES6
function sumByEs(a = 0, b = 0) {
  return a + b;
}
console.log(sumByEs()); // 0

可以清晰的看到代码简洁很多。

在上面介绍了原生js的arguments对象,在ES6中引入了rest参数,用于获取函数的多余参数。

// JS
function sumByJs() {
  console.log(arguments);
}
sumByJs(1, 2, 3, 4);

// ES6
function sumByEs(...num) {
  console.log(num);
}
sumByEs(1, 2, 3, 4);

上述代码值相同,但是原生js的值属于arguments对象,ES6属于数组对象。ES6支持自定义参数名。

箭头函数

箭头函数使函数更加简洁,比如

// JS
function sumByJs(a, b) {
  if (typeof a === 'undefined') a = 0;
  if (typeof b === 'undefined') b = 0;
  return a + b;
}
console.log(sumByJs(1, 2));

// ES6
const sumByEs = (a = 0, b = 0) => a + b;
console.log(sumByEs(1, 2));

箭头函数虽好,但也要注意:

  • 箭头函数没有自己的this对象
  • 不可以当作构造函数,不可以对箭头函数使用new命令,否则会抛出一个错误。
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

this指向问题

箭头函数的this指向一直是个考点,无论Vue还是ES6都会有相应的问题。

// 作为全局函数
const a = () => {
  console.log(this); // window
}
a()

// 作为对象的属性值
const b = {
  a: () => {
    console.log(this); // window
  }
}
b.a();

// 在普通函数中
function c() {
  const d = () => {
    console.log(this); // window
  }
  d();
}
c();

// 在构造函数中
function D() {
  const e = () => {
    console.log(this); // D{}
  }
  e()
}
const d = new D();

上述四种场景打印了箭头函数的this指向,只有第四种创建实例后this才会指向实例,其他均指向window;
所以:箭头函数里面根本没有自己的this,而是引用外层的this

ES2019新增特性

toString()

ES10更新了toString,返回函数代码本身,以前会省略注释和空格。

function a(a, b) {
  // 返回a+b的值
  return a + b;
}
console.log(a.toString())

在业务开发中很少用到这个方法,在调试中可能会遇到,了解即可。

catch 命令的参数省略

try {
  throw new Error('异常')
} catch (err) {
  console.log(err)
}

try {
  throw new Error('异常')
} catch {
  console.log('执行异常')
}

仅做了优化,实际业务中其实还是需要捕获实际的错误信息。

相关资料

相关试题

  • 函数调用的3种方式是什么?
  • 改变this指向的方式有哪些?
  • thissuper有什么区别?
  • call、apply、bind方法的区别?
  • 什么是构造函数?构造函数和普通函数有什么区别?
  • 通过自定义构造函数创建的变量,如何检测它是这个构造函数的实例?
  • 你知道闭包吗?它有什么特点?
  • 简述一下闭包的优点和缺点?
  • 下面代码如何打印出12345
    for(var i=1; i<=5; i++) {
      setTimeout(function() {
        console.log( i );
      }, 1000);
    } 
    
  • ES6对函数的参数做了哪些升级?
  • ES6箭头函数和原生JS函数有什么区别?
  • ES6箭头函数的this指向哪里?
  • ES6的箭头函数能用于构造函数吗?为什么?