1. 箭头函数
ES6
中允许使用箭头函数 =>
来定义函数,箭头函数相当于匿名函数,并且简化了函数定义。
例:
const add = (x, y) => x + y
const fn = (name) => ({ name })
特性:
- 语法简洁,代码量少。
- 没有自己的
this
,它会继承外层作用域的this
。 - 不能作为构造函数,不能用
new
关键字创建对象实例,也不能用作构造函数。 - 不能使用
arguments
对象,它没有自己的arguments
对象,会继承外层作用域的arguments
- 适合短小的函数、或者作用回调函数
2. 普通函数
函数的定义方式通常有三种,函数声明方式
、函数表达式
、使用 Function
构造函数;
2.1、 函数声明方式
function add(a, b) {
return a + b
}
2.2、 函数表达式
const fn = function(name) {
return { name }
}
2.3、 使用Function
构造函数
const sum = new Function("num1", "num2", "return num1 + num2")
2.4、 三种方式的区别
主要从作用域
、效率
、加载顺序
来区分
2.4.1、 作用域
函数声明、函数表达式声明、Function() 使用的都是局部变量
var name = "我是全局变量name"
// 声明式
function a() {
var name = "我是函数a的name"
return name;
}
console.log(a()) // 我是函数a的name
// 表达式
var b = function() {
var name = "我是函数b的name";
return name;
}
console.log(b()) // 我是函数b的name
// Function 构造函数
var c = new Function("const name = '我是函数c的name';return name;")
console.log(c()); // 我是函数c的name
2.4.2、 执行效率
Function()
构造函数效率要低于其他两种方式,尤其是在循环中,因为构造函数每执行一次都要重新编译,并生成新的函数对象
2.4.2、加载顺序
函数声明式在 JavaScript
编译时就会加载到作用域中,而其他两种方式则是在代码执行时加载,在定义之前调用它,会返回 undefined
console.log(typeof f) // function
console.log(typeof c) // undefined
console.log(typeof d) // undefined
function f() {
return "f"
}
var c = function() {
return "c"
}
console.log(typeof c) // function
var d = new Function("return 'd'");
console.log(typeof d) // function
2.5、函数的参数
2.5.1、arguments
arguments 对象的 length 属性显示实参的个数,函数的 length 属性显示形参的个数。
function sum(x, y) {
console.log(arguments.length) // 3
return x + 1;
}
sum(1, 2, 3);
console.log(sum.length); // 2
2.5.1、同名参数
非严格模式下,函数中可以出现同名形参,而且只能访问最后一个出现的形参
function sum(x, x, x) {
return x;
}
console.log(sum(1, 2, 3)); // 3
严格模式下会抛出语法错误 SyntaxError: Duplicate parameter name not allowed in this context
2.6、函数的返回值
默认所有的函数都有返回值,没有 return
时,默认返回内容为 undefined
function sum1(x, y) {
var total = x + y;
}
console.log(sum1()); // undefined
function sum2(x, y) {
return x + y;
}
console.log(sum2(1, 2)); // 3
如果函数调用时,前面加了 new
前缀,且返回值不是一个对象,则返回 this
,如果是一个对象,则返回该对象
function Book() {
this.bookName = "JS 深入浅出"
}
var book = new Book();
console.log(book); // Book { bookName: "JS 深入浅出"}
console.log(book.constructor); // [Function: Book]
function Book() {
return { bookName: "JS 深入浅出"};
}
var book = new Book();
console.log(book); // { bookName: "JS 深入浅出"}
console.log(book.constructor); // [Function: Book]
2.7、匿名函数
匿名函数是一种在JavaScript 中定义的函数方式,他没有给函数起一个具体的名称,匿名函数通常在需要时直接定义使用,而不需要预先命名,这种函数在语法上与普通函数类似,只是省略了函数名。
用途:
- 函数表达式,将匿名函数赋值给变量,可以创建函数表达式,这使得函数能像其他数据类型一样储存在变量中
const add = function(a, b) {
return a + b;
}
console.log(add(3, 5)); // 8
- 作为参数传递,用于回调或特定操作
setTimeout(function() {
console.log("Delayed message")
}, 1000)
- 立即执行函数,可以创建一个私有作用域
(function() {
console.log("IIFE executed")
})();
- 函数属性,将匿名函数分配给对象属性,已创建对象的方法
const obj = {
sayHello: function() {
console.log("Hello")
}
}
obj.sayHello(); // Hello
总之,匿名函数是一种在需要时临时创建函数的方式,适用于需要传递函数、作为参数、创建私有作用域、动态定义函数时;
普通函数的特性:
- 拥有自己的
this
: 普通函数在调用时会有自己的this
上下文,this
的值取决于函数被调用的方式 - 可以作为构造函数:普通函数可以通过
new
关键字创建对象实例,用作构造函数。 - 可以使用
arguments
对象:普通函数拥有自己的arguments
对象,用于获取函数参数。 - 语法相对复杂:需要
function
关键字来定义
3. 箭头函数与普通函数的区别
let fn = (name) => {
console.log(name);
}
let fn2 = function(name) {
console.log(name);
}
console.log(fn);
console.log(fn2);
看起来,箭头函数少了 caller
arguments
prototype
3.1、声明方式不同
- 声明一个普通函数需要
function
来完成,并且使用function
既可以声明成一个具名函数,也可以声明成一个匿名函数 - 声明一个箭头函数则只要使用箭头就可以了
- 箭头函数只能声明匿名函数,但可以通过表达式的方式让箭头函数具名
3.2、箭头函数没有 prototype(原型)
const a = () => {};
console.log(a.prototype); // undefined
const b = () => {};
console.log(b.prototype); // { constructor: f}
3.3、箭头函数不能当成一个构造函数
let fn = (value) => value;
const f = new fn("hi"); // Uncaught TypeError: fn is not a constructor
new
实现原理:
- 创建一个新对象
- 将该对象的原型链连接到构造函数的原型对象上,使其继承构造函数的属性和方法
- 将构造函数中的
this
指向新创建的对象 - 执行构造函数内部的代码,给新对象添加属性和方法
- 如果构造函数没有返回其他对相关,则返回新创建的对象;如果构造函数返回了一个非基本类型的值,则返回这个对象,否则还是返回新创建的对象;
function myNew(fn, ...args) {
// 创建一个新对象
let target = {};
// 将这个空对象的 __proto__指向构造函数的原型
target.__proto__ = fn.prototype;
// 将this 指向空对象
let res = fn.apply(target, args)
// 对构造函数返回值做判断,然后返回
return res instanceof Object ? res : target;
}
因为箭头函数没有自己的this ,他的this 是外层执行环境的this ,且指向不会发生改变。并且箭头函数没有原型 prototype, 没法让他的实例的 proto 指向箭头函数的原型,所以构造函数无法作为构造函数。
3.4、箭头函数不支持 new.target
new
是从构造函数生成实例对象的命令,ES6
为 new
命令引入了一个 new.target
属性,这个属性一般用在构造函数中,返回 new
调用的那个构造函数,如果构造函数不是通过 new
命令或者 Reflect.connstruct()
调用的,new.target
会返回 undefined
,所以这个属性可以用来确定构造函数是怎么调用的;
function fn() {
console.log("fnc", new.target);
}
fn(); // fn: undefined
new fn(); // fn: [Function: fn]
// 箭头函数的 this 指向全局对象时
let fn2 = () => {
console.log("fn2", new.target);
}
fn2; // Uncaught SyntaxError: new.target expression is not allowed here
// 箭头函数this指向普通函数时
function func() {
let test = () => {
console.log(new.target); // 指向函数func: [Function: func]
}
test();
}
new func();
- new.target 属性一般用在构造函数中,返回 new 调用的那个构造函数;
- 箭头函数的 this 指向全局对象,在箭头函数中使用 new.target 会报错
- 箭头函数的 this 指向普通函数,它的 new.target 就是指向改普通函数的引用