js学习2024.7.3

var、let、const

作用域:var 声明的变量是函数作用域或全局作用域。如果在函数内部声明,它的作用域是整个函数;如果在函数外部声明,它的作用域是全局的。
变量提升:var 声明的变量会被提升。可以在声明之前使用,但值是 undefined。
可重复声明:同一作用域内可以多次使用 var 声明同一个变量。

作用域:let 声明的变量是块作用域。
变量提升:let 声明的变量不会被提升。
不可重复声明:同一作用域内不能多次使用 let 声明同一个变量。

作用域:const 声明的变量也是块作用域。
变量提升:const 声明的变量不会被提升。
不可重复声明:同一作用域内不能多次使用 const 声明同一个变量。
不可重新赋值:const 声明的变量必须在声明时初始化,并且不能重新赋值。不过,如果 const 声明的是一个对象或数组,其内容是可以改变的。

函数作用域

### 定义

作用域(scope)指的是变量存在的范围。在 ES5 的规范中,JavaScript 只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。

函数内部定义的变量,会在该作用域内覆盖同名全局变量。

var v = 1;

function f(){
  var v = 2;
  console.log(v);
}

f() // 2
v // 1

上面代码中,变量`v`同时在函数的外部和内部有定义。结果,在函数内部定义,局部变量`v`覆盖了全局变量`v`。

注意,对于`var`命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。

if (true) {
  var x = 5;
}
console.log(x);  // 5

上面代码中,变量`x`在条件判断区块之中声明,结果就是一个全局变量,可以在区块之外读取。



函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。

var a = 1;
var x = function () {
  console.log(a);
};

function f() {
  var a = 2;
  x();
}

f() // 1

上面代码中,函数`x`是在函数`f`的外部声明的,所以它的作用域绑定外层,内部变量`a`不会到函数`f`体内取值,所以输出`1`,而不是`2`。

总之,函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。

参数

js里的函数的参数,不用跟实际传过来的值一一对应

### arguments 对象

由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是`arguments`对象的由来。

`arguments`对象包含了函数运行时的所有参数,`arguments[0]`就是第一个参数,`arguments[1]`就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用

var f = function (one) {
  console.log(arguments[0]);
  console.log(arguments[1]);
  console.log(arguments[2]);
}

f(1, 2, 3)

需要注意的是,虽然`arguments`很像数组,但它是一个对象。数组专有的方法(比如`slice`和`forEach`),不能在`arguments`对象上直接使用。

如果要让`arguments`对象使用数组方法,真正的解决方法是将`arguments`转为真正的数组。

关于slice函数的语法

let fruits = ['apple', 'banana', 'cherry', 'date', 'fig'];

// 提取从索引1开始到索引3的元素
let slicedFruits = fruits.slice(1, 3); // 返回 ["banana", "cherry"]

// 如果不指定end参数,则提取从start到数组末尾的元素
let slicedFruitsToEnd = fruits.slice(1); // 返回 ["banana", "cherry", "date", "fig"]

// 如果start参数是负数,则表示从数组末尾开始计算的位置
let slicedFruitsFromEnd = fruits.slice(-3); // 返回 ["date", "fig"]

// 如果省略start和end参数,则返回数组的一个完整副本
let fruitsCopy = fruits.slice(); // 返回 ["apple", "banana", "cherry", "date", "fig"]

语句与表达式的区别

##### 语句(Statement)

语句是执行动作的指令。它告诉JavaScript执行某些操作,通常会有副作用(即改变程序的状态或环境)。语句的结束通常需要一个分号(`;`),但这不是强制性的,因为在JavaScript中,分号是语句的可选结束符。例如:

// 赋值语句
let x = 10;

// 函数调用语句
doSomething();

// 控制流语句
if (condition) {
  // ...
}

// 循环语句
for (let i = 0; i < 10; i++) {
  // ...
}

##### 表达式(Expression)

表达式是一个计算并返回值的代码片段。它可以是一个变量、一个函数调用、一个算术运算或者任何能够产生值的组合。表达式可以是语句的一部分,但单独的表达式不能作为语句存在(除非它是返回值的函数或语句末尾有分号)。 例如:

// 变量名是一个表达式
let y = 20;

// 算术运算是一个表达式
let sum = 10 + 5; // 这里的 10 + 5 是一个表达式

// 函数调用也是一个表达式,特别是当它返回一个值时
let result = doSomething(); // doSomething() 是一个返回值的表达式

立即调用的函数表达式(IIFE)

根据 JavaScript 的语法,圆括号`()`跟在函数名之后,表示调用该函数。比如,`print()`就表示调用`print`函数。

有时,我们需要在定义函数之后,立即调用该函数。这时,你不能在函数的定义之后加上圆括号,这会产生语法错误。

function(){ /* code */ }();
// SyntaxError: Unexpected token ()

产生这个错误的原因是,`function`这个关键字既可以当作语句,也可以当作表达式。

// 语句
function f() {}

// 表达式
var f = function f() {}

当作表达式时,函数可以定义后直接加圆括号调用。

var f = function f(){ return 1}();
f // 1

上面的代码中,函数定义后直接加圆括号调用,没有报错。原因就是`function`作为表达式,引擎就把函数定义当作一个值。这种情况下,就不会报错。

为了避免解析的歧义,JavaScript 规定,如果`function`关键字出现在行首,一律解释成语句。因此,引擎看到行首是`function`关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。

函数定义后立即调用的解决方法,就是不要让`function`出现在行首,让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面。

(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();

上面两种写法都是以圆括号开头,引擎就会认为后面跟的是一个表达式,而不是函数定义语句,所以就避免了错误。这就叫做“立即调用的函数表达式”(Immediately-Invoked Function Expression),简称 IIFE。

注意,上面两种写法最后的分号都是必须的。如果省略分号,遇到连着两个 IIFE,可能就会报错。

// 报错
(function(){ /* code */ }())
(function(){ /* code */ }())

上面代码的两行之间没有分号,JavaScript 会将它们连在一起解释,将第二行解释为第一行的参数。

推而广之,任何让解释器以表达式来处理函数定义的方法,都能产生同样的效果,比如下面三种写法。

var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();

甚至像下面这样写,也是可以的。

!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();

(function () {
  var tmp = newData;
  processData(tmp);
  storeData(tmp);
}());

eval 命令

`eval`命令接受一个字符串作为参数,并将这个字符串当作语句执行。

eval('var a = 1;');
a // 1

数组的本质

本质上,数组属于一种特殊的对象。`typeof`运算符会返回数组的类型是`object`。

typeof [1, 2, 3] // "object"

但是,对于数值的键名,不能使用点结构

var arr = [1, 2, 3];
arr.0 // SyntaxError

上面代码中,`arr.0`的写法不合法,因为单独的数值不能作为标识符(identifier)。所以,数组成员只能用方括号`arr[0]`表示(方括号是运算符,可以接受数值)。

length 属性

数组的`length`属性,返回数组的成员数量。

['a', 'b', 'c'].length // 3

JavaScript 使用一个32位整数,保存数组的元素个数。这意味着,数组成员最多只有 4294967295 个(232 - 1)个,也就是说`length`属性的最大值就是 4294967295。

只要是数组,就一定有`length`属性。该属性是一个动态的值,等于键名中的最大整数加上`1`。

var arr = ['a', 'b'];
arr.length // 2

arr[2] = 'c';
arr.length // 3

arr[9] = 'd';
arr.length // 10

arr[1000] = 'e';
arr.length // 1001

上面代码表示,数组的数字键不需要连续,`length`属性的值总是比最大的那个整数键大`1`。另外,这也表明数组是一种动态的数据结构,可以随时增减数组的成员。

`length`属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员数量会自动减少到`length`设置的值。

in 运算符

检查某个键名是否存在的运算符`in`,适用于对象,也适用于数组。

var arr = [ 'a', 'b', 'c' ];
2 in arr  // true
'2' in arr // true
4 in arr // false

上面代码表明,数组存在键名为`2`的键。由于键名都是字符串,所以数值`2`会自动转成字符串。

注意,如果数组的某个位置是空位,`in`运算符返回`false`。

var arr = [];
arr[100] = 'a';

100 in arr // true
1 in arr // false

上面代码中,数组`arr`只有一个成员`arr[100]`,其他位置的键名都会返回`false`。

数组遍历

var a = [1, 2, 3];

// for循环
for(var i = 0; i < a.length; i++) {
  console.log(a[i]);
}

// while循环
var i = 0;
while (i < a.length) {
  console.log(a[i]);
  i++;
}

var l = a.length;
while (l--) {
  console.log(a[l]);
}

上面代码是三种遍历数组的写法。最后一种写法是逆向遍历,即从最后一个元素向第一个元素遍历。

数组的`forEach`方法,也可以用来遍历数组

var colors = ['red', 'green', 'blue'];
colors.forEach(function (color) {
  console.log(color);
});
// red
// green
// blue

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值