js学习笔记(2)

78 阅读6分钟
1.函数表达式,函数声明,箭头函数

1.函数
1.1 每个函数都是Function类型的实例,而Function 也有属性和方法,跟其他引用类型(引用值是引用类型的实例)一样,数名就是指向函数对象的指针。
1.2 定义函数的三种方式
函数声明

function sum (num1, num2) {
    return num1 + num2;
}

函数表达式

let sum = function(num1, num2) {
    return num1 + num2;
};

箭头函数

let sum = (num1, num2) => {
    return num1 + num2;
};

箭头函数不能使用arguments(类数组对象,存储函数调用时传入的参数)、super(关键字用于访问对象字面量或类的原型上的属性,或调用父类的构造函数) 和 new.target(new.target指向被new调用的构造函数),也不能用作构造函数。

2.函数名
因为函数名就是指向函数的指针,所以它们跟其他包含对象指针的变量具有相同的行为。这意味着 一个函数可以有多个名称

function sum(num1, num2) {
    return num1 + num2;
}
console.log(sum(10, 10)); // 20
let anotherSum = sum;
console.log(anotherSum(10, 10)); // 20
sum = null;
console.log(anotherSum(10, 10)); // 20

所有函数对象都会暴露一个只读的name 属性,其中包含关于函数的信息

function foo() {}
let bar = function() {};
let baz = () => {};
console.log(foo.name); // foo
console.log(bar.name); // bar
console.log(baz.name); // baz
console.log((() => {}).name); //(空字符串)
console.log((new Function()).name); // anonymous

即使函数没有名称,也会如实显示成空字符串。如果它是使用Function 构造函数创建的,则会标识成"anonymous" 如果函数是一个获取函数、设置函数,或者使用bind()实例化,那么标识符前面会加上一个前缀

function foo() {}
console.log(foo.bind(null).name); // bound foo
let dog = {
    years: 1,
    get age() {
        return this.years;
     },
    set age(newAge) {
        this.years = newAge;
     }
}
let propertyDescriptor = Object.getOwnPropertyDescriptor(dog, 'age');
console.log(propertyDescriptor.get.name); // get age
console.log(propertyDescriptor.set.name); // set age

如果在ECMAScript 中定义了两个同名函数,则后定义的会覆盖先定义

function addSomeNumber(num) {
  return num + 100;
}
function addSomeNumber(num) {
  return num + 200;
}
let result = addSomeNumber(100); // 300

这里,函数addSomeNumber()被定义了两次。第一个版本给参数加100,第二个版本加200。最后一行调用这个函数时,返回了300,因为第二个定义覆盖了第一个定义。

3.参数
在使用function 关键字定义(非箭头)函数时,可以在函数内部访问arguments 对象,从中取得传进来的每个参数值。
arguments 对象是一个类数组对象(但不是Array 的实例),因此可以使用中括号语法访问其中的元素(第一个参数是arguments[0],第二个参数是arguments[1])

function howManyArgs() {
  console.log(arguments.length);
}
howManyArgs("string", 45); // 2
howManyArgs(); // 0
howManyArgs(12); // 1

arguments 对象可以跟命名参数一起使用

function doAdd(num1, num2) {
  if (arguments.length === 1) {
    console.log(num1 + 10);
  } else if (arguments.length === 2) {
    console.log(arguments[0] + num2);
  }
}

如果函数是使用箭头语法定义的,那么传给函数的参数将不能使用arguments 关键字访问,而只 能通过定义的命名参数访问。

function foo() {
console.log(arguments[0]);
}
foo(5); // 5
let bar = () => {
console.log(arguments[0]);
};
bar(5); // ReferenceError: arguments is not defined

4.函数声明与函数表达式
JavaScript 引擎在任何代码执行之前,会先读取函数声明,并在执行上下文中生成函数定义。而函数表达式必须等到代码执行到它那一行,才会在执行上下文中生成函数定义

// 没问题
console.log(sum(10, 10));
function sum(num1, num2) {
  return num1 + num2;
}

以上代码可以正常运行,因为函数声明会在任何代码执行之前先被读取并添加到执行上下文。这个 过程叫作函数声明提升,把发现的函数声明提升到源代码树的顶部。因此即使函数定义出现在调用它们的代码之后,引擎也会把函数声明提升到顶部.
如果把前面代码中的函数声明改为等价的函数表达式,那么执行的时候就会出错

// 会出错
console.log(sum(10, 10));
  let sum = function(num1, num2) {
  return num1 + num2;
};

上面的代码之所以会出错,是因为这个函数定义包含在一个变量初始化语句中,而不是函数声明中。

因为函数名在ECMAScript 中就是变量,所以函数可以用在任何可以使用变量的地方

function callSomeFunction(someFunction, someArgument) {
  return someFunction(someArgument);
}

这个函数接收两个参数。第一个参数应该是一个函数,第二个参数应该是要传给这个函数的值。任何函数都可以像下面这样作为参数传递。

function add10(num) {
  return num + 10;
}
let result1 = callSomeFunction(add10, 10);
console.log(result1); // 20
function getGreeting(name) {
  return "Hello, " + name;
}
let result2 = callSomeFunction(getGreeting, "Nicholas");

callSomeFunction()函数是通用的,第一个参数传入的是什么函数都可以,而且它始终返回调用 作为第一个参数传入的函数的结果
如果是访问函数而不是调用函数,那就必须不带括号, 所以传给callSomeFunction()的必须是add10 和getGreeting,而不能是它们的执行结果。
4.函数内部
函数内部存在特殊的对象:arguments,this,new.target
4.1 arguments
一个类数组对象,包含调用函数时传入的所有参数。这个对象只有以function 关键字定义函数。arguments 对象其实还有一个callee 属性,是一个指向arguments 对象所在函数的指针。

function factorial(num) {
if (num <= 1) {
  return 1;
} else {
  return num * factorial(num - 1);
}

使用arguments.callee 就可以让函数逻辑与函数名解耦

function factorial(num) {
if (num <= 1) {
  return 1;
} else {
  return num * arguments.callee(num - 1);
}

这个重写之后的factorial()函数已经用arguments.callee 代替了之前硬编码的factorial。 这意味着无论函数叫什么名称,都可以引用正确的函数
4.2 this
在标准函数中,this 引用的是把函数当成方法调用的上下文对象,这时候通常称其为this 值(在网页的全局上下文中调用函数时,this 指向windows)

window.color = 'red';
let o = {
  color: 'blue'
};
function sayColor() {
  console.log(this.color);
}
sayColor(); // 'red'
o.sayColor = sayColor;
o.sayColor(); // 'blue'

在箭头函数中,this 引用的是定义箭头函数的上下文

function King() {
this.royaltyName = 'Henry';
// this 引用King 的实例
setTimeout(() => console.log(this.royaltyName), 1000);
}
function Queen() {
this.royaltyName = 'Elizabeth';
// this 引用window 对象
setTimeout(function() { console.log(this.royaltyName); }, 1000);
}
new King(); // Henry
new Queen(); // undefined

4.3 caller
这个属性引用的是调用当前函数的函数

function outer() {
inner();
}
function inner() {
console.log(inner.caller);
}
outer();

以上代码会显示outer()函数的源代码,这是因为ourter()调用了inner(),inner.caller指向outer()。
4.4 new.target
函数始终可以作为构造函数实例化一个新对象,也可以作为普通函数被调用。ECMAScript 6 新增了检测函数是否使用new 关键字调用的new.target 属性.
如果函数是正常调用的,则new.target 的值是undefined;如果是使用new 关键字调用的,则new.target 将引用被调用的构造函数。

function King() {
  if (!new.target) {
  throw 'King must be instantiated using "new"'
}
console.log('King instantiated using "new"');
}
new King(); // King instantiated using "new"
King(); // Error: King must be instantiated using "new"