文章目录
- js 基础应用(变量/函数/作用域)
- 函数提升与变量提升的差异
- this 应用
- js 中的上下文
- 作用域 参数 共享
js 基础应用(变量/函数/作用域)
-
变量声明提升实例
- 在JavaScript中,变量声明会被提升到当前作用域的顶部。例如:
console.log(a); // 输出:undefined var a = 10;
- 解释:虽然变量
a
的赋值语句在console.log
之后,但是变量声明var a
会被提升到作用域的顶部。在代码执行之前,JavaScript引擎会先处理变量声明。此时a
已经被声明,但是还没有赋值,所以输出undefined
。
-
函数提升实例
- 函数声明也会被提升。例如:
sayHello(); // 输出:Hello! function sayHello() { console.log("Hello!"); }
- 解释:函数
sayHello
的声明被提升到了当前作用域的顶部,所以在调用它之前,JavaScript引擎已经知道了这个函数的存在,因此可以正常调用。 - 注意,如果是函数表达式,情况会有所不同:
sayHi(); // 报错:Uncaught TypeError: sayHi is not a function var sayHi = function() { console.log("Hi!"); };
- 解释:这里
var sayHi
这个变量声明被提升了,但是赋值(也就是函数表达式部分)没有被提升。所以在调用sayHi
的时候,它的值是undefined
,并不是一个函数,从而导致报错。
-
作用域实例应用
- 全局作用域和局部作用域
- 全局作用域:
var globalVar = 100; function globalFunction() { console.log(globalVar); } globalFunction(); // 输出:100
- 解释:变量
globalVar
在全局作用域中声明,函数globalFunction
也在全局作用域中定义。函数globalFunction
可以访问全局变量globalVar
。
- 解释:变量
- 局部作用域:
function outerFunction() { var outerVar = 200; function innerFunction() { console.log(outerVar); } innerFunction(); // 输出:200 } outerFunction();
- 解释:
outerFunction
创建了一个局部作用域,变量outerVar
在这个局部作用域中声明。innerFunction
在outerFunction
的内部,它可以访问outerFunction
作用域中的变量outerVar
,这是因为JavaScript的词法作用域规则,内部函数可以访问其外部函数的变量。
- 解释:
- 全局作用域:
- 块级作用域(ES6中的
let
和const
)- 例如:
{ let blockVar = 300; console.log(blockVar); // 输出:300 } console.log(blockVar); // 报错:Uncaught ReferenceError: blockVar is not a defined
- 解释:使用
let
声明的变量具有块级作用域。在这个例子中,变量blockVar
只在花括号{}
内部的块级作用域中有效。在块级作用域外部访问它会导致报错。const
也有类似的块级作用域特性,并且const
声明的变量一旦赋值就不能重新赋值(对于基本数据类型)。例如:
const PI = 3.14; // PI = 3.14159; // 报错:Uncaught TypeError: Assignment to constant variable.
- 解释:使用
- 例如:
- 全局作用域和局部作用域
函数提升与变量提升的差异
- 提升机制的本质差异
- 变量提升:
- 对于使用
var
关键字声明的变量,JavaScript引擎会在执行代码之前,将变量声明提升到当前作用域的顶部。但变量的初始化不会被提升。例如:
console.log(a); // 输出:undefined var a = 10;
- 在这里,变量
a
的声明被提升了,所以在console.log
语句执行时,JavaScript知道有变量a
这个标识符,但是此时a
还没有被赋值,所以它的值是undefined
。
- 对于使用
- 函数提升:
- 函数声明会被整个提升到当前作用域的顶部。这意味着函数的定义在代码执行之前就已经被处理好了,包括函数体内部的逻辑。例如:
sayHello(); // 输出:Hello! function sayHello() { console.log("Hello!"); }
- 函数
sayHello
的整个定义被提升,所以在调用它之前,JavaScript引擎就已经知道这个函数的存在,并且可以正确地执行函数内部的代码。
- 变量提升:
- 提升后的行为差异
- 变量提升后访问行为:
- 变量提升后,如果在变量初始化之前访问它,会得到
undefined
。而且如果在一个作用域内重复使用var
声明同一个变量,JavaScript会忽略后面的声明,例如:
var a = 10; var a; console.log(a); // 输出:10
- 这是因为变量声明被提升后,后面的
var a
声明就相当于被忽略了,变量a
的值还是最初赋值的10
。
- 变量提升后,如果在变量初始化之前访问它,会得到
- 函数提升后访问行为:
- 函数提升后,可以在函数声明之前调用函数。而且如果在同一个作用域内重复定义同名函数,后面的函数定义会覆盖前面的函数定义。例如:
function add(a, b) { return a + b; } function add(a, b) { return a * b; } console.log(add(2, 3)); // 输出:6
- 这里后面定义的
add
函数覆盖了前面的add
函数,所以调用add
函数时,执行的是后面的乘法运算逻辑。
- 变量提升后访问行为:
- 对作用域的影响差异
- 变量提升对作用域的影响:
- 变量提升是基于当前作用域的。在全局作用域中声明的变量可以在全局范围内访问,在函数作用域中声明的变量只能在函数内部访问(不考虑闭包等特殊情况)。例如:
var globalVar = 100; function testFunction() { var localVar = 200; console.log(globalVar); // 输出:100 console.log(localVar); // 输出:200 } testFunction(); console.log(globalVar); // 输出:100 console.log(localVar); // 报错:Uncaught ReferenceError: localVar is not defined
- 变量
globalVar
在全局作用域声明,localVar
在函数testFunction
的局部作用域声明,它们的访问受到作用域的限制。
- 函数提升对作用域的影响:
- 函数提升同样基于作用域。函数内部可以访问外部作用域的变量,但是外部作用域通常不能直接访问函数内部的变量(除非通过返回值等方式)。例如:
var globalVar = 100; function outerFunction() { var outerVar = 200; function innerFunction() { console.log(outerVar); // 输出:200 console.log(globalVar); // 输出:100 } innerFunction(); } outerFunction();
- 函数
innerFunction
可以访问其外部函数outerFunction
中的变量outerVar
以及全局变量globalVar
,体现了函数提升后的作用域访问规则。
- 变量提升对作用域的影响:
this 应用
- 全局应用中的
this
- 在全局环境下(浏览器中的
window
对象或者Node.js中的global
对象),this
通常指向全局对象。 - 例如在浏览器环境中:
console.log(this === window); // true var globalVariable = 10; this.globalVariable; // 可以通过this访问全局变量,等同于window.globalVariable function globalFunction() { console.log(this); // 这里的this指向window } globalFunction();
- 解释:在全局函数内部,
this
同样指向全局对象。因为全局函数实际上是作为全局对象(window
)的方法来执行的。当在全局范围内定义变量时,这些变量实际上是全局对象的属性,所以可以通过this
来访问。
- 在全局环境下(浏览器中的
- 局部函数应用中的
this
- 对象方法中的
this
- 当函数作为对象的方法被调用时,
this
指向该对象。例如:
let person = { name: "Alice", sayHello: function() { console.log("Hello, my name is " + this.name); } }; person.sayHello(); // 输出:Hello, my name is Alice
- 解释:在
sayHello
方法中,this
指向person
对象。所以可以通过this.name
访问person
对象的name
属性。
- 当函数作为对象的方法被调用时,
- 事件处理函数中的
this
- 在HTML事件处理函数中(在浏览器环境下),
this
通常指向触发事件的元素。例如:
<button onclick="console.log(this.textContent)">Click me</button>
- 解释:当按钮被点击时,
this
指向按钮元素本身,this.textContent
可以获取按钮上显示的文本内容。
- 在HTML事件处理函数中(在浏览器环境下),
- 构造函数中的
this
- 在构造函数中,
this
指向新创建的对象实例。例如:
function Person(name) { this.name = name; this.sayHello = function() { console.log("Hello, my name is " + this.name); }; } let person1 = new Person("Bob"); person1.sayHello(); // 输出:Hello, my name is Bob
- 解释:当使用
new
关键字调用Person
构造函数时,this
在构造函数内部指向新创建的person1
对象。通过this.name
为对象添加属性,并且this.sayHello
为对象添加方法。
- 在构造函数中,
- 对象方法中的
- 调用
this
的方法- 作为对象方法调用
- 如前面提到的
person.sayHello()
这种方式,通过对象来调用方法,this
就会指向该对象。
- 如前面提到的
- 使用
call
和apply
方法call
方法:可以用来调用一个函数,并且指定函数内部this
的指向,同时传递参数。例如:
function greet(greeting) { console.log(greeting + ", " + this.name); } let person = {name: "Charlie"}; greet.call(person, "Hi"); // 输出:Hi, Charlie
- 解释:
call
方法的第一个参数是要绑定this
的对象,后面的参数是函数greet
需要的参数。在这里,this
在greet
函数内部就指向了person
对象。 apply
方法:和call
类似,但是传递参数的方式不同。apply
接收两个参数,第一个是要绑定this
的对象,第二个是一个包含函数参数的数组。例如:
function addNumbers(a, b) { return this.sum + a + b; } let calculator = {sum: 10}; let args = [2, 3]; addNumbers.apply(calculator, args); // 输出:15
- 使用
bind
方法bind
方法会创建一个新的函数,新函数内部的this
会永久地绑定到指定的对象上。例如:
function multiply(a, b) { console.log(this.value * a * b); } let multiplier = {value: 2}; let boundMultiply = multiply.bind(multiplier); boundMultiply(3, 4); // 输出:24
- 解释:
bind
方法返回一个新的函数boundMultiply
,这个新函数内部的this
永远指向multiplier
对象。即使将boundMultiply
作为其他对象的方法调用或者在其他作用域中调用,this
的指向也不会改变。
- 作为对象方法调用
- 改变
this
指向的方法总结- 主要有三种方式:
call
、apply
和bind
。它们的区别在于参数传递方式和是否立即执行函数。call
和apply
会立即执行函数,而bind
返回一个新的函数,需要再次调用这个新函数才会执行。这些方法在处理面向对象编程、函数复用以及事件处理等场景中非常有用,可以灵活地控制this
的指向,以满足不同的业务需求。
- 主要有三种方式:
js 中的上下文
-
全局上下文(Global Context)
- 定义:
- 全局上下文是JavaScript代码执行的最外层环境。在浏览器环境中,全局上下文对应的对象是
window
,在Node.js环境中是global
。它包含了所有全局变量和函数,这些变量和函数可以在代码的任何位置访问(除非被函数作用域或块级作用域隐藏)。
- 全局上下文是JavaScript代码执行的最外层环境。在浏览器环境中,全局上下文对应的对象是
- 实例:
- 在浏览器环境下,我们可以这样理解全局上下文:
var globalVariable = 10; function globalFunction() { console.log("This is a global function"); } console.log(window.globalVariable); // 输出:10,说明globalVariable是window对象的属性 console.log(window.globalFunction); // 输出:function globalFunction() {...},说明globalFunction是window对象的属性
- 解释:这里的
globalVariable
和globalFunction
都是在全局上下文中定义的,它们实际上是window
对象的属性和方法。当我们在浏览器中访问window
对象时,可以看到这些定义的变量和函数。 - 在Node.js环境下:
global.sharedVariable = 20; function sharedFunction() { console.log("This is a global function in Node.js"); } console.log(global.sharedVariable); // 输出:20 console.log(global.sharedFunction); // 输出:function sharedFunction() {...}
- 解释:在Node.js中,全局对象是
global
,和浏览器中的window
类似,全局变量和函数是作为global
对象的属性和方法存在的。
- 定义:
-
作用域上下文(Scope Context)
- 定义:
- 作用域上下文决定了变量和函数的可访问性和生命周期。JavaScript有函数作用域和块级作用域。函数作用域是指在函数内部定义的变量只能在函数内部访问,块级作用域(通过
let
和const
声明变量)是指在{}
大括号内定义的变量只能在这个块内访问。
- 作用域上下文决定了变量和函数的可访问性和生命周期。JavaScript有函数作用域和块级作用域。函数作用域是指在函数内部定义的变量只能在函数内部访问,块级作用域(通过
- 函数作用域实例:
- 例如:
function outerFunction() { var outerVariable = 30; function innerFunction() { console.log(outerVariable); // 输出:30,可以访问outerVariable } innerFunction(); } outerFunction(); console.log(outerVariable); // 报错:Uncaught ReferenceError: outerVariable is not defined,无法在函数外部访问outerVariable
- 解释:
outerVariable
是在outerFunction
的函数作用域内定义的,innerFunction
可以访问outerVariable
是因为JavaScript的词法作用域规则,内部函数可以访问其外部函数定义的变量。但是在outerFunction
外部,outerVariable
是不可访问的。
- 块级作用域实例:
{ let blockVariable = 40; console.log(blockVariable); // 输出:40 } console.log(blockVariable); // 报错:Uncaught ReferenceError: blockVariable is not defined,无法在块外部访问blockVariable
- 解释:这里使用
let
声明了blockVariable
,它具有块级作用域,只能在{}
这个块内部被访问。当代码执行到块外部时,变量blockVariable
就不再可访问了。
- 解释:这里使用
- 定义:
作用域 参数 共享
-
通过返回值共享变量(函数作用域)
- 原理:
- 在函数内部定义的变量,默认情况下其作用域仅限于函数内部。但是可以通过将变量作为函数的返回值,使得外部代码能够访问函数内部的变量。
- 示例:
function createCounter() { let count = 0; return function() { count++; return count; }; } let counter = createCounter(); console.log(counter()); // 输出:1 console.log(counter()); // 输出:2
- 解释:
createCounter
函数内部定义了变量count
,它的初始值为0。然后createCounter
返回一个内部函数,这个内部函数可以访问count
变量。当我们调用counter
(也就是createCounter
返回的内部函数)时,count
变量的值会递增并返回。这样就通过返回值的方式,让外部代码能够访问和修改函数内部定义的变量。
- 解释:
- 原理:
-
全局变量共享(全局作用域与函数作用域交互)
- 原理:
- 全局变量可以在函数内部访问和修改,从而实现作用域之间的交互。不过,过度使用全局变量可能会导致代码的可维护性变差。
- 示例:
let globalVariable = 10; function modifyGlobalVariable() { globalVariable += 5; console.log(globalVariable); // 输出:15 } modifyGlobalVariable(); console.log(globalVariable); // 输出:15
- 解释:在这个例子中,
globalVariable
是一个全局变量。modifyGlobalVariable
函数可以访问并修改这个全局变量。在函数内部修改globalVariable
后,其值在函数外部也会发生改变,因为它们引用的是同一个变量。
- 解释:在这个例子中,
- 原理:
-
闭包共享变量(函数作用域与外部作用域)
- 原理:
- 闭包是指有权访问另一个函数内部变量的函数。一个函数返回了另一个函数,并且这个返回的函数引用了外部函数的局部变量,就形成了闭包。通过闭包可以在不同的函数调用之间保持变量的状态,并且可以从外部访问内部变量。
- 示例:
function outerFunction() { let privateVariable = 20; function innerFunction() { console.log(privateVariable); } return innerFunction; } let closureFunction = outerFunction(); closureFunction(); // 输出:20
- 解释:
outerFunction
返回innerFunction
,innerFunction
可以访问outerFunction
内部的privateVariable
,形成了闭包。当我们调用closureFunction
(也就是innerFunction
)时,它能够访问privateVariable
,即使outerFunction
已经执行完毕。这样就实现了从外部访问函数内部变量的效果,并且通过闭包可以在不同的时间点访问和操作这些变量。
- 解释:
- 原理:
-
通过对象属性共享(函数作用域与对象作用域)
- 原理:
- 可以将函数内部的变量作为对象的属性,通过返回对象或者修改对象属性的方式,让外部代码能够访问这些变量。
- 示例:
function createObjectWithVariable() { let internalVariable = 30; return { getVariable: function() { return internalVariable; }, setVariable: function(newValue) { internalVariable = newValue; } }; } let objectWithVariable = createObjectWithVariable(); console.log(objectWithVariable.getVariable()); // 输出:30 objectWithVariable.setVariable(40); console.log(objectWithVariable.getVariable()); // 输出:40
- 解释:
createObjectWithVariable
函数内部定义了internalVariable
,然后返回一个对象,这个对象有两个方法getVariable
和setVariable
。getVariable
方法可以获取internalVariable
的值,setVariable
方法可以修改它的值。通过这种方式,外部代码可以通过对象的方法来访问和修改函数内部的变量。
- 解释:
- 原理: