JavaScript关于函数的总结

this的指向

在 JavaScript 中,this 代表当前函数执行的上下文对象。this 的值是在函数执行时动态确定的,取决于函数的调用方式。下面是 this 可能代表的不同情况:

1. 默认绑定

当函数独立调用时,this 的值指向全局对象(浏览器中是 window 对象,Node.js 中是 global 对象)。

function foo() {
    
    
  console.log(this);
}
foo(); // window(浏览器中)或 global(Node.js 中)

2. 隐式绑定

当函数作为对象的方法调用时,this 的值指向该对象。

var obj = {
    
    
  name: "张三",
  sayName: function() {
    
    
    console.log(this.name);
  }
};
obj.sayName(); // 张三

3. 显示绑定

通过 call、apply 或 bind 方法来改变函数的执行上下文,从而将 this 显式绑定到指定对象上。

function sayName() {
    
    
  console.log(this.name);
}
var obj1 = {
    
     name: "张三" };
var obj2 = {
    
     name: "李四" };
sayName.call(obj1); // 张三
sayName.apply(obj2); // 李四
var sayName2 = sayName.bind(obj1);
sayName2(); // 张三

4. new 绑定

当使用 new 操作符调用构造函数时,this 的值指向新创建的实例对象。

function Person(name) {
    
    
  this.name = name;
}
var p = new Person("张三");
console.log(p.name); // 张三

需要注意的是,如果使用箭头函数,其 this 的值是在定义时确定的,并且不会被动态改变。因此在箭头函数中使用 this,其值通常是其定义时所在的上下文。

闭包

在 JavaScript 中,闭包是指一个函数能够访问并操作其外部作用域中的变量,即使这些变量在函数执行结束后也能够保持其值和状态。闭包在 JavaScript 中是非常常见和重要的概念。

闭包的实现方式是在一个函数内部定义另一个函数,并将其作为返回值返回,这样就形成了一个闭包。被返回的函数可以访问其定义时所在的作用域中的变量,而这些变量在该函数执行结束后不会被销毁,因为它们被闭包所引用。

以下是一个使用闭包实现计数器的示例:

function createCounter() {
    
    
  var count = 0;
  return function() {
    
    
    count++;
    console.log(count);
  };
}
var counter1 = createCounter();
counter1(); // 输出 1
counter1(); // 输出 2
counter1(); // 输出 3
var counter2 = createCounter();
counter2(); // 输出 1
counter2(); // 输出 2

在上述代码中,createCounter 函数返回一个内部定义的匿名函数,并且该匿名函数引用了外部作用域中的变量 count。每次调用 createCounter 函数都会创建一个新的闭包,因此 counter1 和 counter2 是两个独立的计数器。

闭包还有一些特殊的应用,例如使用闭包实现函数柯里化、延迟计算、模块化等。需要注意的是,由于闭包会使得函数中的变量一直保存在内存中,如果过度使用闭包,可能会导致内存泄漏的问题,因此需要注意合理使用闭包。

执行上下文

在 JavaScript 中,执行上下文(Execution Context)是指 JavaScript 代码执行时的环境,它包含了执行代码所需的所有信息,比如变量、函数、作用域、this 等。在每次函数调用时,都会创建一个新的执行上下文。

执行上下文分为三种类型:

  1. 全局执行上下文(Global Execution Context):在 JavaScript 代码开始执行时创建的执行上下文,它是代码中最外层的执行上下文,也是整个程序的默认执行上下文。全局执行上下文只有一个,并且它存在于整个程序的生命周期中。

  2. 函数执行上下文(Function Execution Context):每次函数调用时创建的执行上下文,它包含了函数内部定义的变量、函数、作用域链等信息。

  3. eval 执行上下文(Eval Execution Context):在使用 eval 函数时创建的执行上下文,它包含了被执行的代码片段所需的变量、函数、作用域链等信息。

执行上下文在创建时会经历以下两个阶段:

  1. 创建阶段:执行上下文被创建时,JavaScript 引擎会创建变量对象(Variable Object)、作用域链(Scope Chain)、this 指针等信息。
  2. 执行阶段:执行上下文被创建完成后,JavaScript 引擎会执行其中的代码,并更新变量对象、作用域链等信息。

在创建阶段,执行上下文会按照以下顺序完成一些操作:

  1. 创建变量对象(Variable Object):变量对象是一个存储变量、函数声明等信息的对象,在执行上下文创建时被创建。
  2. 创建作用域链(Scope Chain):作用域链是一个存储当前执行上下文以及所有外层执行上下文变量对象的链表结构。当 JavaScript 引擎在当前上下文中查找变量时,会先在当前变量对象中查找,如果找不到,就会沿着作用域链向上查找。
  3. 确定 this 指针:this 指针是指向函数执行时的当前对象的指针,它在函数调用时被确定。

在执行阶段,执行上下文会按照以下顺序完成一些操作:

  1. 执行代码:JavaScript 引擎会按照代码的顺序执行其中的语句。
  2. 更新变量对象:变量对象中的变量会被更新为执行过程中的最新值。
  3. 执行函数声明:如果代码中存在函数声明,则会在创建阶段将其添加到变量对象中,执行阶段时可以直接调用这些函数。

执行上下文和作用域是 JavaScript 中非常重要的概念,理解它们的运行机制对于编写高质量的 JavaScript 代码非常重要。

原型/原型链

在 JavaScript 中,每个对象都有一个指向它原型的内部链接,这个原型就是通过构造函数创建的对象的原型对象。可以通过 prototype 属性来访问构造函数的原型对象。

当我们创建一个对象时,JavaScript 引擎会首先查找该对象本身是否有某个属性或方法,如果没有,它就会在该对象的原型对象中查找,如果还是没有找到,就会继续在原型对象的原型对象中查找,直到找到该属性或方法,或者查找到原型链的末端为止。

原型对象可以实现属性和方法的继承。当我们访问某个对象的属性或方法时,如果该对象本身没有该属性或方法,JavaScript 引擎就会沿着原型链向上查找,直到找到该属性或方法为止。

以下是原型链的一些常见概念:

**1. 原型(Prototype):**每个对象都有一个原型对象,用于实现属性和方法的继承。

**2. 原型链(Prototype Chain):**由于每个对象的原型对象也是一个对象,因此原型对象也有自己的原型对象,它们组成了一个链式结构,称为原型链。

  1. 构造函数(Constructor):用于创建对象的函数,通过构造函数创建的对象都有一个原型对象,该原型对象通过构造函数的 prototype 属性设置。

  2. __proto__ 属性:每个对象都有一个 __proto__ 属性,用于指向该对象的原型对象,可以通过该属性访问原型链上的属性和方法。但是,在实际开发中不推荐使用 __proto__ 属性,而是使用 Object.getPrototypeOf 方法来获取对象的原型对象。

下面是一个简单的例子,演示了如何使用原型实现属性和方法的继承:

// 定义一个构造函数
function Person(name, age) {
    
    
  this.name = name;
  this.age = age;
}

// 在 Person 构造函数的原型对象上定义一个方法
Person.prototype.sayHello = function() {
    
    
  console.log(`Hello, my name is ${
      
      this.name} and I am ${
      
      this.age} years old.`);
};

// 创建一个 Person 对象
const person1 = new Person('Alice', 25);

// 调用 Person 对象的 sayHello 方法
person1.sayHello(); // 输出:Hello, my name is Alice and I am 25 years old.

// 创建一个 Student 对象,继承自 Person 对象
function Student(name, age, grade) {
    
    
  Person.call(this, name, age); // 调用父类构造函数,初始化父类的属性
  this.grade = grade;
}

// 通过原型继承,使 Student 对象也拥有 sayHello 方法
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

// 创建一个 Student 对象
const student1 = new Student('Bob', 18, 10);

// 调用 Student 对象的 sayHello 方法

作用域和作用域链

在 JavaScript 中,作用域是指变量(包括函数)的可访问范围,即变量可以被访问的代码区域。在 JavaScript 中,有两种作用域:全局作用域和函数作用域。

全局作用域是指在代码的最外层定义的变量,它可以在整个程序中被访问。函数作用域是指在函数内部定义的变量,它只能在函数内部被访问。

JavaScript 中的作用域链是由一系列嵌套的函数作用域所组成的,它形成了一个层层嵌套的作用域链。当代码在某个作用域中访问变量时,JavaScript 引擎会先在当前作用域中查找该变量,如果找到了就直接使用该变量,如果没有找到就继续在外层作用域中查找,直到找到该变量或者查找到全局作用域为止。

作用域链的生成过程是在函数定义的时候就已经确定的,它是由函数定义时的作用域环境所决定的。当函数执行时,会创建一个新的执行环境,该执行环境的作用域链会包含函数定义时的作用域环境。如果函数内部定义了其他函数,那么新的函数的作用域链也会包含外层函数的作用域环境,这样就形成了一个嵌套的作用域链。

作用域和作用域链的概念是 JavaScript 中比较基础和重要的概念,对于理解 JavaScript 中的变量、函数和闭包等都非常有帮助。下面是一个简单的例子,演示了作用域链的使用:

// 全局作用域
const globalVar = 'globalVar';

function outer() {
    
    
  // 外层函数作用域
  const outerVar = 'outerVar';

  function inner() {
    
    
    // 内层函数作用域
    const innerVar = 'innerVar';

    console.log(innerVar); // 输出 'innerVar'
    console.log(outerVar); // 输出 'outerVar'
    console.log(globalVar); // 输出 'globalVar'
  }

  inner();
}

outer();

在上面的例子中,全局作用域包含了一个全局变量 globalVar,外层函数作用域包含了一个变量 outerVar,内层函数作用域包含了一个变量 innerVar。当执行 inner 函数时,它首先在自己的作用域中查找 innerVar 变量,然后在外层作用域中查找 outerVar 变量和全局作用域中查找 globalVar 变量,最后输出这些变量的值。由于作用域链的机制,内部函数可以访问外部函数的变量,而外部函数不能访问内部函数的变量。这就是闭包的概念,即一个函数可以访问它定义时所在的作用域中的变量,即使这个函数在定义时所在的作用域已经销毁了。

下面是一个使用闭包的例子:

function outer() {
    
    
  const outerVar = 'outerVar';

  function inner() {
    
    
    console.log(outerVar);
  }

  return inner;
}

const innerFn = outer();
innerFn(); // 输出 'outerVar'

在上面的例子中,outer 函数返回了一个内部函数 inner,并且在 inner 函数中访问了 outerVar 变量。当调用 outer 函数时,它会创建一个新的执行环境,并把当前作用域链中的 outer 函数作为作用域环境压入作用域链中。当执行完 outer 函数后,它的作用域环境会被销毁,但是由于 inner 函数引用了 outerVar 变量,这个变量的值并没有被释放,所以可以在 inner 函数中访问到这个变量。

猜你喜欢

转载自blog.csdn.net/qq_48439911/article/details/130270348