4、Python3 ES6 Javascript 基础

作者: Brinnatt 分类: python 道 发布时间: 2023-05-21 09:44

4.1、Nodejs

Nodejs 是服务器端运行 JavaScript 的开源、跨平台运行环境。

Nodejs 原始作者瑞安·达尔(Ryan Dahl),于 2009 年发布,使用了 V8 引擎,并采用事件驱动、非阻塞、异步 IO 模型。

2010 年,npm 软件包管理器诞生,通过它,可以方便的发布、分享 Nodejs 的库和源代码。

Nodejs 4.0 引入了 ES6 语言特性。

我们学习 JS,就让它跑在最新版的 Nodejs 上,为了调试方便,也为了使用最新的 ES2017 特性。

4.2、安装

国内可以去阿里云镜像站下载:

https://mirrors.aliyun.com/nodejs-release/

windows:https://mirrors.aliyun.com/nodejs-release/v20.0.0/node-v20.0.0-x64.msi

linux:https://mirrors.aliyun.com/nodejs-release/v20.0.0/node-v20.0.0-linux-x64.tar.xz

python3_nodejs

msi 安装会增加 path 路径。

4.3、开发

4.3.1、文档

搜索 MDN,Mozilla Developer Network,提供非常完善 HTML、CSS、JS 等的技术资料。

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript

指南 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide 非常好的 JS 文档。

使用任何一种文本编辑器,都可以开发 JS,此次使用微软的 Visual Studio Code 开发。

4.3.2、Visual Studio Code

下载 https://code.visualstudio.com/Download

支持 windows、mac、Linux 平台。

有代码自动完成功能,还可以安装 Node exec 插件,将写的 js 跑在 nodejs 上。

python3_NodeExec

Node Exec 插件快捷键:F8 运行 js 脚本,F9 停止。

前端开发中,JS 脚本一般来说是为了控制浏览器的网页的,这里使用了 VSCode,只是为了开发调试方便。

4.3.3、注释

和 C、Java 一样,// 单行注释,/* 注释 */ 多行注释,也可以用在语句中。

str = 'hello' + /*comment*/' brinnatt'
console.log(str)

4.3.4、常量和变量

标识符:标识符必须是字母、下划线、美元符号和数字,但必须是字母、下划线、美元符号开头,依然是不能数字开头就行。标识符区分大小写。

声明:

  • var 声明一个变量

  • let 声明一个块作用域中的局部变量

  • const 声明一个常量

JS 中的变量声明和初始化是可以分开的。

var a // 只是声明,a为undefined
let b
console.log(1,a,b)

a = 1
b = 'a string'
console.log(2,a,b)

//const c // 可以吗?
const c = 100 // 常量必须声明时赋值,之后不能再改
console.log(c)

//c = 200 // 不可以更改
var y // 只是声明,y值为undefined
var x = 5 // 规范的声明并初始化,声明全局或局部变量。
x = 6 // 不规范的初始化,不推荐。在严格模式下会产生异常。在赋值之前不能引用,因为它没有声明。一旦这样赋值就是全局作用域。
function hello()
{
    var a // 只是声明,a为undefined,作用域在函数中
    a = 100
    console.log(a)
}

console.log(a) // 未声明变量a,异常

// a = 200 // 不能提升作用域
// var a = 200; hello(); // var提升作用域,提升到全局,上面的console.log(a)不会抛异常,但是undefined

常量和变量的选择:如果明确知道一个标识符定义后不再修改,应该尽量声明成 const 常量,减少被修改的风险,减少 Bug。javascript 语言的包容性很强,含有隐式转换,写代码时尽量准确声明变量,不要随意提升作用域。

4.3.5、数据类型

序号 名称 说明
1 number 数值型,包括整型和浮点型
2 boolean 布尔型,true 和 false
3 string 字符串
4 null 只有一个值 null
5 undefined 变量声明未赋值的;对象未定义的属性
6 symbol ES6 新引入类型
7 object 类型 是以上基本类型的复合类型,是容器

ES 是动态语言,弱类型语言。

虽然先声明了变量,但是变量可以重新赋值任何类型。

// 类型转换
// 弱类型
console.log('=====string=====')
console.log(1, '-->', a = 3 + 'magedu', typeof (a))
console.log(2, '-->', a = null + 'magedu', typeof (a))
console.log(3, '-->', a = undefined + 'magedu', typeof (a))
console.log(4, '-->', a = true + 'magedu', typeof (a))

// 数字
console.log('=====number=====')
console.log(11, '-->', a = null + 8, typeof (a))
console.log(12, '-->', a = undefined + 8, typeof (a)) //undefined没法转换成一个对应的数字
console.log(13, '-->', a = true + 8, typeof (a)) // 1
console.log(14, '-->', a = false + 8, typeof (a))

// boolean
console.log('=====bool=====')
console.log(21, '-->', a = null + true, typeof (a))
console.log(22, '-->', a = null + false, typeof (a))
console.log(23, '-->', a = undefined + true, typeof (a)) //undefined没法转换成一个对应的数字
console.log(24, '-->', a = undefined + false, typeof (a)) // 1
console.log(25, '-->', a = null & true, typeof (a))
console.log(26, '-->', a = undefined & true, typeof (a))

// 短路
console.log(31, '-->', a = null && true, typeof (a)) // 逻辑运算符,null 直接就是false短路
console.log(32, '-->', a = false && null, typeof (a)) // 逻辑运算符,false短路返回false
console.log(33, '-->', a = false && 'magedu', typeof (a)) // boolean
console.log(34, '-->', a = true && 'magedu', typeof (a)) // 字符串
console.log(35, '-->', a = true && '', typeof (a)) // 字符串

// null
console.log('=====null=====')
console.log(41, '-->', a = null + undefined, typeof (a))

弱类型,不需要强制类型转换,会隐式类型转换。

NaN, Not a Number,转换数字失败。

规律总结:

  • 遇到字符串,加号就是拼接字符串,所有非字符串隐式转换为字符串。

  • 如果没有字符串,加号把其他所有类型都当数字处理,非数字类型隐式转换为数字。undefined 特殊,因为它都没有定义值,所以是一个特殊数字 NaN。

  • 如果运算符是逻辑运算符,短路符,返回就是短路时的类型。没有隐式转换。

除非你十分明确,否则不要依赖隐式转换。写代码的时候,往往为了程序的健壮,请显式的转换。

注意:以上的原则不要死记,忘了就实验,或者显示的类型转换,记忆力并非总是靠谱。

4.3.6、字符串

将一个值使用 '' 单引号或者 "" 双引号引用起来就是字符串。

ES6 提供了反引号定义一个字符串,可以支持多行,还支持插值。

let a = 'abc'
let b = "135"
let c = `line1
        line2
    line3
` // 支持多行
console.log(c)

// 字符串插值,要求在反引号字符串中。python3.6支持
let someone = "tom", age = 19
console.log(`Hi, my name is ${someone}. I am ${age}`)

4.3.7、转义字符

名称 说明
\0 Null 字节
\b 退格符
\f 换页符
\n 换行符
\r 回车符
\t Tab (制表符)
\v 垂直制表符
\' 单引号
\" 双引号
\ 反斜杠字符(\)
\XXX 由从0到377最多三位八进制数XXX表示的 Latin-1 字符。例如,\251是版权符号的八进制序列
\xXX 由从00和FF的两位十六进制数字XX表示的Latin-1字符。例如,\xA9是版权符号的十六进制序列
\uXXXX 由四位十六进制数字XXXX表示的Unicode字符。例如,\u00A9是版权符号的Unicode序列。
见Unicode escape sequences (Unicode 转义字符)
\u{XXXXX} Unicode代码点 (code point) 转义字符。例如,\u{2F804} 相当于Unicode转义字符
\uD87E\uDC04的简写

4.3.8、字符串操作方法

字符串操作方法很多,但和 Python 类似。

let school = 'brinnatt'
console.log(1, '-->', school.charAt(2)) // i
console.log(2, '-->', school[2]) // i
console.log(3, '-->', school.toUpperCase()) // BRINNATT
console.log(4, '-->', school.concat('.com')) // 连接
console.log(5, '-->', school.slice(3)) // 切片,支持负索引
console.log(6, '-->', school.slice(3, 5))
console.log(7, '-->', school.slice(-2, -1))
console.log(8, '-->', school.slice(-2))

let url = "www.brinnatt.com"
console.log(9, '-->', url.split('.'))
console.log(10, '-->', url.substr(7, 2)) // 返回子串从何处开始,取多长
console.log(11, '-->', url.substring(7, 10)) // 返回子串,从何处开始,到什么为止

let s = 'brinnatt.edu'
console.log(12, '-->', s.indexOf('na')) // 4
console.log(13, '-->', s.indexOf('ed', 10)) // -1
console.log(14, '-->', s.replace('.edu', '.com'))
s = ' \tbri nnatt \r\n'
console.log(15, '-->', s.trim()) // 去除两端的空白字符。trimLeft、trimRight是非标函数,不要用

4.3.9、数值型 number

在 JS 中,数据均为双精度浮点型范围只能在 -(2^53 -1)2^53 -1 之间,整型也不例外。

数字类型还有三种符号值:+Infinity(正无穷)-Infinity(负无穷)NaN (not-a-number非数字)

二进制 0b00100B110

八进制 0755。注意 0855,将被认作十进制,因为 8 不在八进制中。ES6 中最好使用 0o 前缀表示八进制。

十六进制 0xAA0Xff

指数表示 1E3(1000)2e-2(0.02)

常量属性:

var biggestNum = Number.MAX_VALUE;
var smallestNum = Number.MIN_VALUE;
var infiniteNum = Number.POSITIVE_INFINITY;
var negInfiniteNum = Number.NEGATIVE_INFINITY;
var notANum = Number.NaN;

console.log(biggestNum)
console.log(infiniteNum)
console.log(notANum)

数字的方法:

方法 描述
Number.parseFloat() 把字符串参数解析成浮点数,和全局方法 parseFloat() 作用一致
Number.parseInt() 把字符串解析成特定基数对应的整型数字,和全局方法 parseInt() 作用一致
Number.isFinite() 判断传递的值是否为有限数字
Number.isInteger() 判断传递的值是否为整数
Number.isNaN() 判断传递的值是否为 NaN

内置数学对象 Math。

Math 提供了绝对值、对数指数运算、三角函数运算、最大值、最小值、随机数、开方等运算函数,提供了 PI 值。

console.log(Math.PI)
console.log(Math.abs(-1))
console.log(Math.log2(16))
console.log(Math.sqrt(2))
console.log(Math.random()) // (0, 1)

4.3.10、运算符

4.3.10.1、算数运算符

+ - * / % 等运算符和 Python 一样。

console.log(1 / 2) // 0.5自然除
console.log(1 / 0) // 无异常,返回无穷
console.log(5 % 3)
console.log(parseInt(1 / 2)) // 0
console.log(parseInt(3 / 2)) // 1
console.log(Math.floor(3 / 2)) // 1
console.log(Math.ceil(3 / 2)) // 2
console.log(Math.round(3 / 2)) // 2
console.log(Math.round(1 / 2)) // 1

++--,单目运算符,代表变量自增、自减:

  • i++ 先用 i,用完之后 i 再自增加 1。
  • ++i i 先自增,再使用 i。
let i = 0
let a = i++
console.log(a, i) // 打印什么
console.log(a, i++) // 打印什么
a = ++i
console.log(a, i) // 打印什么

挑战题:

let i = 0;
let a = ++i+i+++i+++i;
console.log(a); // 答案是几?

此题来自 C、C++、Java 的面试题:

1、单目运算符优先级高于双目运算符。

2、加号 + 是双目运算符,两边的表达式必须先计算好。

let i = 0;
let a = ++i + i++ + i++ + i; // 等价于 (++i) + (i++) + (i++) + i
console.log(a); // 1 + 1 + 2 + 3

4.3.10.2、比较运算符

>、<、>=、<= 没有什么区别
!=、==
!==、===

== 宽松相等,进行类型转换,
=== 严格相等,不进行类型转换
console.log(100 > '200') // false
console.log(300 > '200') // true
console.log(300 > '2000') // false
console.log(3000 > '2a') // false
console.log('3000' > '2000') // true

// 宽松比较
console.log(300 == '300') // true
console.log('200' == '200') // true

// 严格比较 ===
console.log(300 === '300') // false
console.log('200' === '200') // true

从上面的比较中,我们起先以为前几行是隐式转换为字符串的,但是后来发现转换为数字,当 3000 > '2a' 比较是犯难了。

使用宽松比较的时候,尽可能确保比较的类型相同,否则会引起隐式转换,而且隐式转换的规则很复杂不好把控。

如果不知道类型是否一致,但是就是要求一定要相等,那么请使用 ===!==

建议比较的时候,一律使用 ===!==

4.3.10.3、逻辑运算符

&&、||、! 与、或、非。

这些运算符和其他高级语言都一样,支持短路

4.3.10.4、位运算

& | ^ ~ << >> 位与、位或、异或、取反、左移、右移,和 Python 一样。

4.3.10.5、三元运算符

条件表达式?真值:假值

等价于简单的if...else结构

if (条件表达式) {
    真值
}
else {
    假值
}

console.log(('3' > 30)?'真':'假')

4.3.10.6、逗号操作符

JS 运行多个表达式写在一起:

let a = 4 + 5, b = true, c = a > 20 ? 't' : 'f'
console.log(a) //9
console.log(c) //f

4.3.10.7、其他

名称 说明
instanceof 判断是否属于指定类型
typeof 返回类型字符串
delete delete 操作符, 删除一个对象(an object)或一个对象的属性(an object's property)或者一个数组中某一个键值(an element at a specified index in an array)
in 如果指定的属性在对象内,则返回 true
console.log('a' instanceof String) // false
console.log(1 instanceof Number) // false
a = new String('b')
console.log(a instanceof String) // true
console.log(new Number(1) instanceof Number) // true
console.log(a instanceof Object) // true
console.log(typeof ('a')) //string
console.log(typeof 'a') //string

instanceof 要求必须明确使用类型定义变量,就是对象必须是 new 关键字声明创建的。它可以用于继承关系的判断。

typeof 就是返回对象的类型。

delete 删除对象、属性、数组元素。

x = 42;
var y = 43;
let z = 60;
myobj = new Number();
myobj.h = 4; // create property h
console.log(delete x); // returns true (can delete if declared implicitly)
console.log(delete y); // returns false (cannot delete if declared with var)
console.log(delete z); // returns false
console.log(delete Math.PI); // returns false (cannot delete predefined properties)
console.log(delete myobj.h); // returns true (can delete user-defined properties)
console.log(delete myobj); // returns true (can delete if declared implicitly)
console.log('~~~~~~~~~~~~~~~~~~~~')

var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
for(var i=0;i<trees.length;i++)
    console.log(trees[i])

console.log('==================')
delete trees[3]; // 数组中元素被删除,但空着的位置是undefined
for(var i=0;i<trees.length;i++)
    console.log(trees[i])

in 判断属性是否在对象内:

let trees = new Array("redwood", "bay", "cedar", "oak", "maple");
console.log(0 in trees); // returns true ,0在数组对象的index中
console.log(3 in trees); // returns true ,3在数组对象的index中
console.log(6 in trees); // returns false,6不在数组对象的index中
console.log("bay" in trees); // return false,bay不是属性,它是值
console.log("length" in trees); // returns true,length是对象的属性
console.log('~~~~~~~~~~~~~~~~~~~~')

delete trees[3];
console.log(3 in trees); // return false
for(var i=0;i<trees.length;i++)
    console.log(trees[i]);
console.log('~~~~~~~~~~~~~~~~~~~~')

// Custom objects
let mycar = {
    color: "red",
    year: 1998
};
console.log("color" in mycar); // returns true
console.log("model" in mycar); // returns false
console.log('year' in mycar) // true

4.3.10.8、运算符优先级

参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence

4.3.10.9、表达式

基本表达式,和 Python 差不多,解析式也和 Python 的相似,但在 ES6 中非标准不推荐。生成器推荐使用生成器函数,ES6 开始支持。

function* inc() {
    let i = 0;
    let j = 7;
    while (true) {
        yield i++;
        if (!j--) return 100;
    }
}
let gen = inc()
for (let i = 0; i < 10; i++)
    console.log(gen.next());

每次调用 next() 方法返回一个对象,这个对象包含两个属性:value 和 done,value 属性表示本次 yield 表达式的返回值,done 属性为布尔类型。done 是 false 表示后续还有 yield 语句执行,如果执行完成或者 return 后,done 为 true。

4.3.11、JS 语句块

JS 使用大括号构成语句块。

ES6 之前语句块是没有作用域的,从 ES6 开始支持块作用域,let 只能在块作用域内可见。

function hello() {
    let a = 1;
    var b = 2;
    c = 3
}

//let d = 100
if (1) {
    let d = 4;
    var e = 5;
    f = 6
    if (true) {
        console.log(d)
        console.log(e)
        console.log(f)
        console.log('-------------')
        g = 10
        var h = 11
    }
}
//console.log(a) // 不可见
//console.log(b) // 不可见
//console.log(c) // 不可见?
//console.log(d) // 块作用域使用let,不可见;但是块外的d可见
console.log(e) // 块作用域使用var,可见
console.log(f) // 块作用域隐式声明,可见
console.log(g) // 可见
console.log(h) // 可见

4.3.12、JS 流程控制

4.3.12.1、条件分支

if (cond1) {
}
else if (cond2) {
}
else if (cond3) {
}
else {
}
条件的False等效
    false
    undefined
    null
    0
    NaN
    空字符串 ("")
其它值都将被视为True

4.3.12.2、switch...case 分支语句

switch (expression) {
    case label_1:
        statements_1
        [break;]
    case label_2:
        statements_2
        [break;]
    ...
    default:
        statements_def
        [break;]
}

这里最大的问题,就是穿透问题,一定要在 case 中恰当的使用 break 语句,否则就会继续顺序向下执行。

let x = 5
switch (x) {
    case 0:
        console.log('zero')
        break;
    case 1:
        console.log('one');
    case 2:
        console.log('two');
    case 3:
        console.log('three');
        break;
    case 5:
        console.log('five, stop?')
    case 4:
        console.log('four');
    default:
        console.log('other')
    // break;
}

switch...case 语句都可以写成多分支结构。

4.3.12.3、for 循环

// C风格for循环
for ([initialExpression]; [condition]; [incrementExpression]) {
    statement
}
for (let i = 0; i < 10; i++) {
    console.log(i)
}
console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
for (var x = 0, y = 9; x < 10; x++, y--) {
    console.log(x * y)
}
console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
for (let i = 0; i < 10; i += 3) {
    console.log(i)
}

4.3.12.4、while 循环和 do...while 循环

while (condition)
    statement

条件满足,进入循环,条件为真,继续循环。

do
    statement
while (condition);

先进入循环,然后判断,为真就继续循环。

let x = 10;
while (x--) {
    console.log(x);
}

do {
    console.log(x);
} while (x++ < 10)

4.3.12.5、练习

九九乘法表,使用 JS 实现。

for (let i = 1; i < 10; i++) {
    line = '';
    for (let j = 1; j <= i; j++)
        line += `${j}*${i}=${i * j} `;
    console.log(line)
}

4.3.12.6、for...in 循环

对象操作语句 for...in 用来遍历对象的属性。

for (variable in object) {
    statements
}
// 数组
let arr = [10, 20, 30, 40];
console.log(arr[1]) // 20
for (let x in arr)
    console.log(x); // 返回索引
for (let index in arr)
    console.log(`${index} : ${arr[index]}`); // 插值

// C风格
for (let i = 0; i < arr.length; i++)
    console.log(arr[i]);

// 对象
let obj = {
    a: 1,
    b: 'brinnatt',
    c: true
};
console.log(obj.a);
console.log(obj['b']);
console.log(obj.d); // undefined
console.log('~~~~~')

for (let x in obj)
    console.log(x); // 属性名

for (let key in obj) // 返回数组的index
    console.log(`${key} : ${obj[key]}`);

for in 循环返回的是索引或者 key,需要间接访问到值。

数组反正返回的是索引,C 风格 for 循环操作可能方便点。根据个人喜好选择。

对象用 for in 合适。

4.3.12.7、for...of 循环

ES6 的新语法:

// for of
let arr = [1, 2, 3, 4, 5]
let obj = {
    a: 1,
    b: 'brinnatt',
    c: true
}
for (let i of arr) { // 返回数组的元素
    console.log(i)
}
for (let i of obj) { // 异常,不可以迭代
    console.log(i)
}

注意:for ... of 不能迭代对象。

原因是,of 后面必须是一个迭代器(TypeError: obj[Symbol.iterator] is not a function)。

可类比 python 中的 for in,例如 for x in [ ]。

4.3.12.8、break、continue

break 结束当前循环。

continue 中断当前循环,直接进入下一次循环。

4.3.12.9、for 迭代的差别

function sum(arr) {
    for (let x in arr) { // 遍历index或对象属性
        console.log(x, typeof (x), arr[x]);
    }
    for (let x of arr) { // 遍历元素
        console.log(x, typeof (x));
    }
    for (let x = 0; x < arr.length; x++) { // 自己定义索引数值遍历
        console.log(x, typeof (x), arr[x]);
    }
}
sum([3, 6, 9]);

4.3.13、JS 函数

function 函数名(参数列表) {
    函数体;
    return 返回值;
}
function add(x, y) {
    return x + y;
}
console.log(add(3, 5));

4.3.13.1、函数表达式

使用表达式来定义函数,表达式中的函数名可以省略,如果这个函数名不省略,也只能用在此函数内部。

// 匿名函数
const add = function (x, y) {
    return x + y;
};
console.log(add(4, 6));

// 有名字的函数表达式
const sub = function fn(x, y) {
    return x - y;
};
console.log(sub(5, 3));
//console.log(fn(3, 2)); // fn只能用在函数内部

// 有名字的函数表达式
const sum = function _sum(n) {
    if (n == 1) return n;
    return n + _sum(--n) // _sum只能内部使用
}
console.log(sum(4));

4.3.13.2、函数、匿名函数、函数表达式

函数和匿名函数,本质上都是一样的,都是函数对象,只不过函数有自己的标识符——函数名,匿名函数需要借助其它的标识符而已。

区别在于,函数会声明提升,函数表达式不会。

console.log(add(4, 6));
function add(x, y) { // 声明提升
    return x + y;
};

//console.log(sub(5, 3)); // sub未定义
const sub = function (x, y) {
    return x - y;
};
console.log(sub(5, 3));

4.3.14、高阶函数

高阶函数:函数作为参数或返回一个函数。

完成一个计数器 counter:

const counter = function () {
    let c = 0;
    return function () {
        return ++c;
    };
};

const c = counter()
console.log(c())
console.log(c())
console.log(c())

完成一个 map 函数,可以对某一个数组的元素进行某种处理。

const map = function (arr, fn) {
    let newarr = [];
    for (let i in arr) {
        newarr[i] = fn(arr[i]);
    }
    return newarr
}
console.log(map([1, 2, 3, 4], function (x) { return ++x }));

map 的生成器实现:

var map = function* (fn, arr) {
    for (i in arr)
        yield fn(arr[i]);
};
let newarr = map(x => x + 10, [1, 2, 3, 4]);
for (x of newarr)
    console.log(x);

4.3.15、箭头函数

箭头函数就是匿名函数,它是一种更加精简的格式。将上例中的匿名函数更改为箭头函数。

const map = function (arr, fn) {
    let newarr = [];
    for (let i in arr) {
        newarr[i] = fn(arr[i]);
    }
    return newarr
}

// 以下三行等价
console.log(map([1, 2, 3, 4], (x) => { return x * 2 }));
console.log(map([1, 2, 3, 4], x => { return x * 2 }));
console.log(map([1, 2, 3, 4], x => x * 2));

箭头函数参数:

  • 如果一个函数没有参数,使用 ()

  • 如果只有一个参数,参数列表可以省略小括号 ()

  • 多个参数不能省略小括号,且使用逗号间隔。

箭头函数返回值:

如果函数体部分有多行,就需要使用 ,如果有返回值使用 return。

如果只有一行语句,可以同时省略大括号和 return。

只要有 return 语句,就不能省略大括号。console.log(map([1,2,3,4], x => {return ++x})),有 return 必须有大括号。

如果只有一条非 return 语句,加上大括号,函数就成了无返回值了,例如 console.log(map([1,2,3,4], x => {x*2})); 加上了大括号,它不等价于 x => {return x*2}。因此,记住 x => x*2 这种正确的形式就行了。

4.3.16、函数参数

4.3.16.1、普通参数

一个参数占一个位置,支持默认参数:

const add = (x, y) => x + y;
console.log(add(4, 5));

const add1 = (x, y = 5) => x + y;
console.log(add1(4, 6));
console.log(add1(4));

那如果有这样一个函数:

const add2 = (x=6,y) => x+y;

这可以吗?尝试使用一下:

console.log(add2());
console.log(add2(1));

console.log(add2(y=2,z=3)); // 可以吗?

上面 add2 的调用结果分别为 NaN、NaN、5,为什么?

1、JS 中并没有 Python 中的关键字传参

2、JS 只是做参数位置的对应

3、JS 并不限制默认参数的位置

add2() 相当于 add(6, undefined)

add2(1) 相当于 add(1, undefined)

add2(y=2,z=3) 相当于 add2(2,3),因为 JS 没有关键字传参,但是它的赋值表达式有值,y=2 就是 2,z=3 就是 3。建议,默认参数写到后面,这是一个好的习惯。

4.3.16.2、可变参数

JS 使用 ... 表示可变参数(Python 用 * 收集多个参数)

const sum = function (...args) {
    let result = 0;
    for (let x in args) {
        result += args[x];
    }
    return result;
};
console.log(sum(3, 6, 9))

4.3.16.3、arguments 对象

函数的所有参数会被保存在一个 arguments 的键值对字典对象中。

(function (p1, ...args) {
    console.log(p1);
    console.log(args);
    console.log('----------------');
    console.log(arguments); // 字典
    for (let x of arguments)
        console.log(x);
})('abc', 1, 3, 5)

ES6 之前,arguments 是唯一可变参数的实现。

ES6 开始,不推荐,建议使用可变参数。为了兼容而保留。

注意,使用箭头函数,取到的 arguments 不是我们想要的,如下:

((x, ...args) => {
    console.log(args); // 数组
    console.log(x);
    console.log(arguments); // 不是传入的值
})(...[1, 2, 3, 4]);

4.3.16.4、参数解构

和 Python 类似,Js 提供了参数解构,依然使用了... 符号来解构。

const add = (x, y) => { console.log(x, y); return x + y };
console.log(add(...[100, 200]))
console.log(add(...[100, 200, 300, 3, 5, 3]))
console.log(add(...[100]))

Js 支持参数解构,不需要解构后的值个数和参数个数对应。

4.3.17、函数返回值

python 中可以使用 return 1,2 返回多值,本质上也是一个值,就是一个元组。Js 中呢?

const add = (x, y) => { return x, y };
console.log(add(4, 100)); // 返回什么?

表达式的值

类 C 的语言,都有一个概念——表达式的值。

赋值表达式的值:等号右边的值。

逗号表达式的值:类 C 语言,都支持逗号表达式,逗号表达式的值,就是最后一个表达式的值。

a = (x = 5, y = 6, true);
console.log(a); // true

b = (123, true, z = 'test')
console.log(b)

function c() {
    return x = 5, y = 6, true, 'ok';
}
console.log(c()); // ok

所以,JS 的函数返回值依然是单值。

4.3.18、作用域

// 函数中变量的作用域
function test() {
    a = 100;
    var b = 200;
    let c = 300;
}

// 先要运行test函数
test()

console.log(a);
console.log(b); // 不可见
console.log(c); // 不可见
// 块作用域中变量
if (1) {
    a = 100;
    var b = 200;
    let c = 300;
}

console.log(a);
console.log(b);
console.log(c); // 不可见

function 是函数的定义,是一个独立的作用域,其中定义的变量在函数外不可见。

var a = 100 可以提升声明,也可以突破非函数的块作用域。

a = 100 隐式声明不能提升声明,在 “严格模式” 下会出错,但是可以把变量隐式声明为全局变量。建议少用。

let a = 100 不能提升声明,而且不能突破任何的块作用域。推荐使用。

function show(i, arg) {
    console.log(i, '-->', arg)
}

// 作用域测试
x = 500;
function fn() {
    let z = 400;
    {
        var o = 100; // var 作用域当前上下文
        show(1, x);
        t = 'free'; // 此语句执行后,t作用域就是全局的,不推荐
        let p = 200;
    }
    var y = 300;
    show(2, z);
    show(3, x);
    show(4, o);
    show(5, t);
    //show(6,p); // 异常,let出不来上一个语句块
    {
        show(7, y);
        show(8, o);
        show(9, t);
        {
            show(10, o);
            show(11, t);
            show(12, z);
        }
    }
}

// 先执行函数
fn()

//show(13,y); // 异常,y只能存在于定义的上下文中,出不了函数
show(14, t); // 全局,但是严格模式会抛异常

//show(15,o) // 看不到o,异常原因同y
show(16, z); // 变量声明提升,var声明了z,但是此时还没有赋值
var z = 10;
const m = 1
//m = 2 // 常量不可以重新赋值

严格模式:使用 "use strict",这条语句放到函数的首行,或者 js 脚本首行。

标签云