函数属性、方法和构造函数 | 青训营笔记

157 阅读9分钟

这是我参与「第四届青训营 」笔记创作活动的第4天

在做大项目的过程中,发现我们需要对浏览器某些内置对象的基本方法做一些修改或增强,要完成这个步骤,就需要我们对于函数属性、方法和构造函数有深刻了解

1.length属性

函数的length属性是只读的

这个属性明确了函数的 arity

——函数声明的形参列表中的形参数量the number of parameters it declares in its parameter list,

——函数期望的实参的数量the number of arguments that the function expects

——函数的剩余参数不算,因为不符合这个属性的本意,剩余参数不是被期待的

2.name属性

函数的只读name属性

  1. 指定了函数定义时使用的名称(如果函数是用名称定义的)
  2. 未命名函数表达式在第一次创建时赋值给的变量或属性的名称

此属性在编写调试或错误消息时非常有用writing debugging or error messages

3.prototype属性

除箭头函数外,所有函数都有一个prototype属性

该属性引用一个称为原型对象的对象refers to an object known as the prototype object

每个函数都有不同的原型对象。

当函数用作构造函数时,新创建的对象会继承原型对象的属性。

4.call() 和apply()方法

call()apply() 允许你间接的调用一个函数,好像这个函数是某个对象的方法一样 allow you to indirectly invoke a function as if it were a method of some other object.

call()apply() 的第一个参数就是调用函数的对象the object on which the function is to be invoked

这个参数就是调用上下文并且在函数体中是this关键字的值 this argument is the invocation context and becomes the value of the this keyword within the body of the function

要将函数 f() 作为对象 o 的方法调用(不传递参数),您可以使用 call()apply()都可以 To invoke the function f() as a method of the object o (passing no arguments), you could use either call() or apply():

f.call(o); 
f.apply(o);

//回想一下方法的本质和面向对象编程
//call和apply本身是函数的方法,在调用的过程中好像让函数成为某个对象的方法
//o-f-call 或者 o-f-apply

以上代码和下面的代码很相似(假设o之前没有1个叫m的属性)

o.m = f; // Make f a temporary method of o. 
o.m(); // Invoke it, passing no arguments. 
delete o.m; // Remove the temporary method.

箭头函数继承了定义它们的上下文的 this 值。

但这不能被 call()apply() 方法覆盖override。

如果您在箭头函数上调用其中任何一个方法,第一个参数将被有效地忽略

因为如果不忽略第一个参数o,那么o就会成为箭头函数的this值,但是箭头函数的this值永远是继承来的

call() 在第一个调用上下文参数之后的任何参数都是传递给被调用函数的值

并且这些参数不会被箭头函数忽略

要将两个数字传递给函数 f() 并像调用对象 o 的方法一样调用它,使用如下代码:

f.call(o, 1, 2);

apply()方法类似于call()方法,只不过传递给函数的参数是指定为数组的

f.apply(o, [1,2]);

如果一个函数被定义为接受任意数量的参数,则 apply() 方法允许您对任意长度数组的内容调用该函数 invoke that function on the contents of an array of arbitrary length

在 ES6 及更高版本中,我们可以只使用扩展运算符

但您可能会看到使用 apply() 的 ES5 代码:

要在不使用扩展运算符的情况下查找数字数组中的最大数,可以使用 apply() 方法将数组元素传递给 Math.max() 函数:

let biggest = Math.max.apply(Math, arrayOfNumbers);
//Math.max()本身就是Math对象的方法,本质上也是一个函数
//因此第一个参数就是Math
//函数嵌套函数,返回原来函数的一部分
// This function takes a function and returns a wrapped version 这个函数接受一个函数参数并返回一个包装的版本
function timed(f) { 
       return function(...args) { // Collect args into a rest parameter array 将 args 收集到一个剩余参数数组中
             console.log(`Entering function ${f.name}`); 
             let startTime = Date.now(); 
             try {// Pass all of our arguments to the wrapped function 将我们所有的参数传递给被包装的函数
               return f(...args); // Spread the args back out again 再次将 args数组 展开
             }
             finally { // Before we return the wrapped return value, print elapsed time. 
                  console.log(`Exiting ${f.name} after ${Date.now()-startTime}ms`); 
             } 
         }; 
}
//timed函数返回了外包装函数function,这个外包装函数定义里将参数收集到剩余参数数组中
//外包装函数里面返回了作为一个参数的函数调用结果值,产生这个结果值需要把剩余参数数组全部展开

// Compute the sum of the numbers between 1 and n by brute force 
function benchmark(n) { 
       let sum = 0; 
       for(let i = 1; i <= n; i++) sum += i; 
       return sum; 
}

// Now invoke the timed version of that test function 
timed(benchmark)(1000000) // => 500000500000; this is the sum of the numbers
//方法嵌套方法,返回原来方法的一个版本
// Replace the method named m of the object o with a version that logs messages before and after invoking the original method. 
function trace(o, m) { 
     let original = o[m]; // Remember original method in the closure. 在闭包中记住原始方法
     o[m] = function(...args) { // Now define the new method. 
         console.log(new Date(), "Entering:", m); // Log message.
         let result = original.apply(this, args); // Invoke original. 
         console.log(new Date(), "Exiting:", m); // Log message.
         return result; // Return result. 
      }; 
}

//它使用apply()方法而不是spread操作符
//通过这样做,它能够使用与wrapper method相同的参数和相同的this值来调用wrapped method

//如果不使用apply,使用spread那只能是这样
o.m = f; // Make f a temporary method of o. 
o.m(); // Invoke it, passing no arguments. 

o[m](...arg);
但这样就是循环引用

5.The bind() 方法

bind()的主要目的是将函数绑定到对象。

当您在函数f上调用bind()方法并传递一个对象o时,该方法将返回一个新函数。

调用新函数(作为函数)就是将调用原始函数f作为o的方法。

传递给新函数的任何参数都会传递给原始函数 注意是新函数g的参数

function f(y) { return this.x + y; } // This function needs to be bound 
let o = { x: 1 }; // An object we'll bind to
let g = f.bind(o); // Calling g(x) invokes f() on o 
g(2) // => 3 
let p = { x: 10, g }; // Invoke g() as a method of this object 
p.g(2) // => 3: g is still bound to o, not p.

箭头函数从定义它们的环境继承其 this 值,并且该值不能被 bind() 覆盖

因此如果前面代码中的函数 f() 被定义为箭头函数,则绑定将不起作用

调用 bind() 的最常见用例是使非箭头函数表现得像箭头函数

通过绑定让非箭头函数成为某个对象的方法,也就继承了包含环境中的this值

//MDN example
//The simplest use of bind() is to make a function that, no matter how it is called, is called with a particular this value.  
//bind() 最简单的用法是创建一个函数,无论它如何调用,都以特定的 this 值调用。

//A common mistake for new JavaScript programmers is to extract a method from an object, then to later call that function and expect it to use the original object as its this (e.g., by using the method in callback-based code).JavaScript程序员的一个常见错误是从对象中提取一个方法,然后稍后调用该函数,并期望它使用原始对象作为它的this(例如,通过在基于回调的代码中使用该方法

//Without special care, however, the original object is usually lost. Creating a bound function from the function, using the original object, neatly solves this problem。然而,如果没有特别的注意,原来的对象通常会丢失。使用原始对象从函数创建一个绑定函数,巧妙地解决了这个问题

this.x = 9;    // 'this' refers to global 'window' object here in a browser
const module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX();
//  returns 81

const retrieveX = module.getX;
retrieveX();
//  returns 9; the function gets invoked at the global scope

//  Create a new function with 'this' bound to module
//  New programmers might confuse the
//  global variable 'x' with module's property 'x'
const boundGetX = retrieveX.bind(module);
boundGetX();
//  returns 81

bind() 方法不仅仅是将函数绑定到对象。 它还可以执行部分应用partial application

在第一个参数之后传递给 bind() 的任何参数都与 this 值一起绑定, 注意是bind的参数

any arguments you pass to bind() after the first are bound along with the this value

bind() 的这个部分应用特性确实适用于箭头函数

部分应用是函数式编程中functional programming的一种常用技术,有时也称为柯里化currying

let sum = (x,y) => x + y; // Return the sum of 2 args 
let succ = sum.bind(null, 1); // Bind the first argument to 1
succ(2) // => 3: x is bound to 1, and we pass 2 for the y argument 

function f(y,z) { return this.x + y + z; } 
let g = f.bind({x: 1}, 2); // Bind this and y 
g(3) // => 6: this.x is bound to 1, y is bound to 2 and z is 3

bind() 返回的函数的 name 属性是调用 bind() 的函数的 name 属性,前缀为“bound”

The name property of the function returned by bind() is the name property of the function that bind() was called on, prefixed with the word “bound”

6.The toString() Method

与所有 JavaScript 对象一样,函数具有 toString() 方法。

ECMAScript 规范要求此方法返回一个遵循函数声明语句语法的字符串。

在实践中,这个 toString() 方法的大多数(但不是全部)实现都会返回函数的完整源代码

Built-in内置函数通常返回一个字符串,其中包含类似“[native code]”之类的内容作为函数体。

7.The Function() Constructor

因为函数是对象,所以有一个 Function() 构造函数可以用来创建新函数

const f = new Function("x", "y", "return x*y;");
//基本类似
const f = function(x, y) { return x*y; };

Function() 构造函数需要任意数量的字符串参数。

最后一个参数是函数体的文本; 它可以包含任意 JavaScript 语句,用分号隔开。

构造函数的所有其他参数都是指定函数参数名称的字符串。

如果您要定义一个不带参数的函数,您只需将一个字符串(函数体)传递给构造函数。

请注意,Function() 构造函数没有传递任何参数来指定它创建的函数的名称。 与函数字面量一样,Function() 构造函数创建匿名函数anonymous functions

其他要点:

  1. Function() 构造函数允许在运行时动态创建和编译 JavaScript 函数allows JavaScript functions to be dynamically created and compiled at runtime。

  2. Function() 构造函数解析函数体parses the function body并在每次调用时创建一个新的函数对象creates a new function object each time it is called。

    1. 如果对构造函数的调用出现在循环中或经常调用的函数中,则此过程可能效率低下。
    2. 相比之下,出现在循环中的嵌套函数和函数表达式不会在每次遇到它们时重新编译recompiled
  3. 它创建的函数不使用词法作用域; 相反,它们总是像顶级函数一样编译always compiled as if they were top- level functions

let scope = "global"; 
function constructFunction() { 
    let scope = "local"; 
    return new Function("return scope"); // Doesn't capture local scope! 
}

// This line returns "global" because the function returned by the Function() constructor does not use the local scope. 
constructFunction()() // => "global"

//如果使用词法作用域,即使用定义函数的作用域的binding,应该返回“local”

结束啦 祝各位阅读愉快~~

image.png