Help:JavaScript

来自滚动的天空Wiki

JavaScript,简称JS,是一门动态弱类型、基于原型、面向对象的脚本语言,作为开发Web页面的脚本语言而出名,但也被用到许多非浏览器环境中。JavaScript的标准是由ECMA国际制定的ECMAScript(简称ES),它规定了语法和语义,具有多个版本。目前,除Internet Explorer外,绝大多数浏览器都已支持ES6。

本页面介绍了一些JavaScript基础内容,建议有一定编程基础的用户阅读。

变量[编辑源代码]

JavaScript中变量(variable)分为全局(global)变量和局部(local)变量。全局变量可以在任何作用域被访问,而局部变量只能在自己的作用域内被访问。所有的全局变量都是globalThis对象中的属性,而在浏览器中globalThis就是window对象。

JavaScript使用varletES6关键字声明、初始化一个变量。声明时初始化是可选的,如果不初始化变量的值则为undefined。如果不使用关键字,且之前变量未被声明,则定义的变量一定是全局变量。在严格模式中,不能用这种方式定义全局变量,必须使用window.varName = value来定义。定义已经声明的变量不需要再写关键字。

同一语句定义多个变量会从左往右依次初始化,而不是评估所有值再赋值。这一点与Lua和Python不同,而与某些通用结构化语言(如C、C#等)相似。想要像Lua和Python那样,可以使用数组解构

var a = 1;
var b = 114, c = 514, d;
var e;

a = 2; // 定义已经声明的变量不需要再写关键字
var b = 1919; // 可以但没有必要
var c; // 不会改变c的值
z = "Global"; // 全局变量
"use strict";
window.variable = "RS"; // 可行
variable = "RS"; // 不可行

在全局作用域中,varlet声明的都是全局变量,而在局部作用域中都是局部变量。同一语句块内,var声明的变量可以由var再次声明,且值不会变化。而let的声明既不能被重新声明,也不能用来重新声明已经声明的变量。

// 不行
var a = 1;
let a = 2;
// 也不行
let b = 1;
var b = 2;
// 更不行
let c = "JS";
let c = "CSS";

var声明的局部变量都处于函数作用域内,而let声明的局部变量处于其所在的最小语句块的作用域。for循环初始化语句声明的let变量会持续到整个循环结束,而循环体内的let变量只持续到一个循环周期结束。

function demo(c) {
    if (c) {
        var a = 114;
        let b = 514;
    }
    // a为514,没有b
    {
        var x = 1919;
        let y = 810;
        {
            var z = 114514;
        }
    }
    // x为1919,z为114514,没有y
    for (let i = 0; i < a; i++) {
        var aa = i + 1;
        let bb = i + 2;
        // 第1次,i为0,aa为1,bb为2;第2次,i为1,aa为2,bb为3
    }
    // 运行后,没有i和bb,aa为115
}

另外,变量声明(带关键字)的值是undefined,而变量定义(不带关键字)的值就是变量被赋予的值。利用该原理可实现多变量赋值。

// 括号一般省略不写
var a = (var b = 5); // a为undefined


var b;
var a = (b = 5); // a为5
// 可写作var a = b = 5;

其他赋值符[编辑源代码]

JavaScript有一些运算符作为运算和赋值的简略形式:

x += y 等价于 x = x + y
x -= y x = x - y
x *= y x = x * y
x /= y x = x / y
……

此外,自增和自减运算符也可分别看作x += 1x -= 1的二次简略形式。

常量ES6[编辑源代码]

常量(constant)声明是ES6新增的特性。常量通过const关键字来声明,且声明时必须初始化。常量声明后不可更改其值,但如果其值为对象,更改对象属性是允许的。与变量类似,常量也有自己的作用域,其特点与let类似。

const PI = math.pi;
pi = 114514; // 会报错
const a; // 不可行
const obj = {};
obj.a = 1; // 可以
obj = {a: 1} // 不可以

数据类型[编辑源代码]

JavaScript是动态弱类型语言,变量没有类型,值有类型,分为下面几种。变量类型可以通过typeof运算符关键字取得,该一元运算符返回其后表达式的类型的字符串。

undefined[编辑源代码]

未定义类型,undefined的类型。它不是对象,故不能获取或设置其属性。与字符串相加时它会隐式转化为"undefined",与数字相加时会得到NaN

number[编辑源代码]

数字类型。JavaScript不细分浮点、整形、长整形。数字类型的原型与Number.prototype严格相等,但不是Number类的实例。使用构造器new Number()生成的数字是Number类的实例,但属于对象类型。不要使用构造器,因为这会导致许多问题。

任何正数除以零得到正无穷Infinity,任何负数除以零得到负无穷-Infinity0 / 0、两个undefined相加及undefined与数字相加等不合法操作会得到NaN(Not a Number)。它不等于任何值,包括其自身,也不大于或小于任何值,但它仍然属于数字类型。它具有传染性,与其它正常数字运算也会得到NaN。因此涉及数字的运算需要留意代码运行过程中有无NaN产生。不像Lua,这两者是可以用字面量获取的。

string[编辑源代码]

字符串类型,与许多语言类似,使用引号表达,字符串内使用相同引号需要加上一个反斜杠。JavaScript不区分“字符”和“字符串”。字符串类型的原型与String.prototype严格相等,但不是String类的实例。使用构造器new String()生成的字符串是String类的实例,但属于对象类型。不要使用构造器,因为这会导致许多问题。

转义序列[编辑源代码]

同其他语言类似,JS字符串有转义序列。如果在不可转义字符前加反斜杠,对该字符无影响。

转义序列
转义字符 意义
\' 表示一个单引号
\" 表示一个双引号
\\ 表示一个反斜杠
\0 表示一个NULL字符
\b 表示一个退格符
\r 表示一个回车符
\v 表示一个垂直制表符
\f 表示一个换页符
\n 表示一个换行符
\t 表示一个制表符
\uXXXX 表示一个Unicode字符,XXXX为其十六进制编码
\xXX 表示一个字节,XX为其十六进制编码(00-FF)
\XXX 表示一个字节,XXX为其八进制编码(000-377)

模板字符串ES6[编辑源代码]

模板字符串是ES6新增的特性,使用字符`(ASCII 96号字符,反引号)表示。字符串内可以用${val}嵌入表达式的值。

"abc\n\td"
var name = 'RS\'s boxes';
var number = 2;
var templateString = `I have ${number.toString()} ${name}!`; // I have 2 RS's boxes!
// 表达式的值若非字符串类型会隐式转化为字符串,因此此处.toString()可省略

boolean[编辑源代码]

布尔值类型,包括真(true)和假(false)。比较运算符的返回值都是布尔值。当布尔值参与到数字运算中时,会转化为1或0。这一点需要尤其注意,因为JavaScript没有连比较语法糖,所以如果使用连比较可能得到意想不到的结果而又不会报错。

var a = 6;
console.log(7 > a > 5); // false(从左往右运算,7 > 6为true,转化为数字为1,1 > 5为假)
// 正确写法是7 > a && a > 5

与前面几种类似,不应当使用new Boolean构造器。

function[编辑源代码]

函数类型。函数表示的是一系列命令组成的子程序。函数有参数和返回值,如果没有显式返回值则返回值为undefined。返回一个值使用return。函数返回值后,不再执行后面内容,因此应当将返回语句写在一个代码块的末尾。与Lua不同,JavaScript函数返回值只能有一个。[1]参数分为形参(parameters)和实参(arguments)。形参是函数定义时的参数,实参是实际调用时传入的参数。两者的个数不一定相等。参数是函数作用域中的局部变量。

定义一个函数的基本格式是:

// 具名函数
function func_name(param1, param2, ...) {
    function_body
}
// 匿名函数
var func_xx = function(param1, param2, ...) {
    function_body
}

注意:在局部作用域中用第一种方式只能将函数定义为局部变量。

函数调用的基本格式是:func(arg1, arg2, ...)。这是一个表达式,解释它时会运行函数块中的代码,并得到其返回值。

arguments[编辑源代码]

arguments是一个带值的关键字,它存储了(非箭头)函数调用时的所有实参。

arguments类似于数组,可以对其进行索引以获得实参,并用length属性获取其长度,但除此之外它没有任何别的数组属性。

在非严格模式之下,当形参没有默认参数、剩余参数和解构赋值时,arguments和实参变量会始终保持一致,修改其中的一个则会影响另一个。反之,则不会相互影响。例如:

function demo(a, b) {
    console.log(arguments[0], arguments[1], arguments[2], a, b);
    arguments[0] = 114;
    arguments[1] = 514;
    arguments[2] = 1919;
    console.log(arguments[0], arguments[1], arguments[2], a, b);
}
demo(810);
// 810 undefined undefined 810 undefined
// 114 514 1919 114 undefined
demo(8, 10);
// 8 10 undefined 8 10
// 114 514 1919 114 514
demo(8, 1, 0);
// 8 1 0 8 1
// 114 514 1919 114 514

function demo2(a, b) {
    "use strict";
    console.log(arguments[0], arguments[1], arguments[2], a, b);
    arguments[0] = 114;
    arguments[1] = 514;
    arguments[2] = 1919;
    console.log(arguments[0], arguments[1], arguments[2], a, b);
}
demo(810);
// 810 undefined undefined 810 undefined
// 114 514 1919 810 undefined
demo(8, 10);
// 8 10 undefined 8 10
// 114 514 1919 8 10
demo(8, 1, 0);
// 8 1 0 8 1
// 114 514 1919 8 1

一般地,不推荐更改arguments的值。

在非严格模式下,可以用calleecaller获取当前函数本身(被调用函数)和调用本函数的函数(如果在全局作用域调用则为undefined)。在严格模式下,访问这两个属性会报错。

this[编辑源代码]

this是一个带值的关键字,它的值是(非箭头)函数作为对象属性被调用时(obj[key]()obj.method())对象(obj)的值。同时,它也是构造器函数中实例本身的值。通常把作为对象属性的函数称为“方法”。

如果作为普通函数调用,this的值为undefined。在全局作用域中,当严格模式未启用,this的值为window,反之则为undefined

除了一般方式,还可以用函数的func.call(thisArg, arg1, arg2, ...)func.apply(thisArg, argsArray)调用函数。在这两种方式中,需要用第一个参数指定this的值。

var obj = {
    method: function(val) {
        this.prop = val
    },
    prop: 0
}
this.prop = 114514; // 等价于window.prop = 114514,严格模式报错
console.log(window.prop, obj.prop); // 114514 0
obj.method(114514); // 等价于obj.method.call(obj, 114514)或obj.method.apply(obj, [114514])
console.log(window.prop, obj.prop); // 114514 114514
var func = obj.method;
func(114514) // 相当于func.call(undefined, 114514)或func.apply(undefined, [114514])
// 会报错,因为this在调用时为undefined,不能对undefined定义属性

new关键字[编辑源代码]

new运算符关键字可以添加到函数调用前,表示函数作为一个构造器被调用。解释构造器调用表达式时,首先会以函数的prototype属性(任何一个非箭头函数创建时都会自动附上prototype,其中有一个constructor属性,其值为该函数本身)作为原型生成一个对象,然后这个对象会作为构造器函数的this值运行构造器函数。构造器函数不应当有返回值,如果它的返回值不是对象,将不影响得到的对象,但如果返回是对象且不是构造出的对象,则构造出的对象会被返回的对象替换。

function A() {
    this.b = 1;
    return "114"
}
var a = new A();
console.log(typeof a); // object
console.log(a instanceof A) // true,因为new A()得到原型为A的实例
function B() {
    this.c = 1919;
    return new Error("你是一个一个")
}
var b = new B();
console.log(b instanceof B) // false,因为new B()得到一个Error对象
console.log(b instanceof Error) // true

有的函数不能用new调用;有的函数只能用new调用,如ES6类的构造器函数;有的两者都可以,如Number,用new调用返回对象,不用则返回数字类型。又如jQuery.Deferred(),它用两种方法调用是等价的,因为它会检查this的值是不是自身的实例,不是则会重新用new调用自身。

箭头函数[编辑源代码]

ES6中新增了箭头函数,作为匿名函数的一个简略形式。其基本语法为(param1,param2) => expr(param1, param2) => { function_body }。第一种形式相当于(param1, param2) => { return expr }。第一种形式中,如果expr是一个对象字面量,这个对象必须用圆括号包裹避免被解释为第二种形式。如果有且只有一个形参,那么参数列表两侧的括号可以省略。

var f = a => {a: a};
var g = a => ({a: a});
console.log(f(1)); // undefined,因为花括号被理解为语句块
console.log(g(1)); // {a: 1}
// 相当于
var func = (a) => {
    return {a: a};
}

箭头函数没有自己的thisarguments。它们两个的值为箭头函数被定义的作用域中的值。

var a;
var o = {
    getArrow() {
        return () => {
            this.a = 1;
            a = arguments
        }
    }
}

o.getArrow()(114);
console.log(o.a, a[0]); // 1 114


与上面几种数据类型不同,函数构造器构造的对象属于函数类型,且函数类型都是函数类的实例。函数构造的语法为new Function(code_string, arg1name, arg2name, ...),构造出的函数可用.call().apply()方法调用。但是,通常使用字符串变量构造函数是危险的,因为输入的字符串是未知的,因此建议不要使用。

symbol[编辑源代码]

object[编辑源代码]

对象类型。JavaScript中几乎所有值都是对象,但不一定是对象类型。凡非上面几种类型的对象皆属于对象类型。


  1. Python也只有一个,之所以看到返回语句中有几个逗号隔开的值是因为返回了一个元组,这个元组可以进行拆包,于是看起来就非常像多个。