一、语法
1.1 严格模式
在<script> 开头加上“use strict" 就行,也可以针对某个方法采用严格模式,直接在function的函数体开头加上use strict" 即可。
1.2 语句
语句末尾加不加分号,其实是建议加分号的。方便删除空行来压缩代码。
1.3 关键字与保留字
除了跟java中一样的关键字外,还有一些特殊的,比如typeof、var、delete、function、yield等
保留字:有些跟java中一样,特殊的有enum 、let、await等。
二、变量
2.1 var关键字和声明提升
var 真正做到类型推断且可以改变类型, 比kotlin要强一点。
var message; 直接打印,其值就是undefined;
var message;
var msg = "x";
info = 1; // 不用var修饰,它就是全局变量
msg = 11;
console.log(message); // undefined
console.log(msg); // 11
如果定义变量时没有var修饰,那它就是全局变量。
声明提升(hoist)就是可以在声明前访问到该变量,因为使用var关键字声明的变量会自动提升到函数作用域顶部, 这点也比kotlin厉害。
也可以反复多次使用var声明同一个变量。
function foo(){
console.log(msg);
var msg = 100;
}
function foo2(){
var msg = 1;
var msg = 100;
var msg = 1200;
console.log(msg);
}
foo(); // undefined
foo2(); // 1200
之所以 foo方法不报错,就是因为此方法等同于
function foo(){
var msg;
console.log(msg);
msg = 100;
}
注意:只是不报错,但是值还是undefined,不是100哈。
2.2 let声明
let声明是块作用域,而var声明是函数作用域。块作用域是函数作用域的子集。
let在同一个作用域中不允许出现同名声明。
let不存在声明提升这个特性。
let在全局作用域中声明的变量不会成为window对象的属性,但是var声明的变量则会。
var name = "hah";
name2 = "xxx";
console.log(window.name); // hah
console.log(name2); // xxx
let age = 100;
console.log(window.age); // undefined
可不可以直接就认为:var声明的变量就是java类中的成员变量,而let声明的变量则只是java 方法中的局部变量。
既然是这样,那for循环中用var修饰的var i = 0;的这个变量i就一下子会渗透到循环体外面。
for(var i = 0;i<5;i++) {
i++;
}
console.log(i);// 6
for(let b = 0;b<5;b++) {
b++;
}
console.log(b); // ReferenceError: b is not defined
2.3 const声明
行为跟let一致,只是在声明时要初始化变量,不能重复声明,且不能修改,声明的作用域是是块作用域。 就把他当常量来看。
三、数据类型
原始类型--简单数据类型:Undefined、Null、Boolean、Number、String 、 Symbol
复杂数据类型:Object(一种无序名值对集合)
3.1 typeof操作符
let msg = 'xx';
let ss;
function aa(){};
console.log(typeof msg); // string
console.log(typeof null); // object
console.log(typeof ss); // undefined
console.log(typeof aa); // function
null直接用typeof操作符,得到的结果是object。
函数严格来讲也是对象,也有自己的属性可以用typeof区分函数和对象。
3.2 undefined类型
该类型只有一个值 undefined,在var 或者let 只声明了变量,但是没有初始化时,就相当于赋值“undefined”
let message = undefined;
这个是没有赋值的哈,message还是undefined;
如果变量没有声明,直接使用会报错,但是有个场景特殊就是用typeof时
let msg ;
console.log(typeof msg); // undefined
console.log(typeof aa); // undefined 这也是 undefined哈!!!!!
console.log(bb);// Uncaught ReferenceError: bb is not defined
undefined是一个假值,可以用来作为条件判断。
let msg ;
if(!msg){
console.log("undefined=false"); // undefined=false
}
3.3 Null类型
只有一个值 null,但是typeof null是 object类型。
undefined值是由null值派生而来,所以 undefined = null;
console.log(undefined == null); //true
null也是一个假值,可以用来做if else判断。
3.4 Boolean类型
true / false两个值,以往我们在其他语言中发现if语句中0 表示false 1表示true,其实是默默给你自动执行了Boolean(XXX)的转换。
let message = "xxx";
let messageBoolean = Boolean(message);
console.log(messageBoolean);// true
if(message){
console.log("print");//print
}
始终记得:
“”(空串)、0、NaN、null、undefined都是false
N/A(不存在)、非空字符串、任意非null对象、非空字符串都是true。
3.5 Number类型
一般十进制,八进制是0开头,如果八进制是无效的八进制,比如079,那就当成是十进制的79等。也可以使用0o来表示八进制,十六进制是以0x开头。
3.5.1 浮点值
浮点值使用的内存空间是存储整数值的两倍,所以ECMAScript总是想法设法把值转换成正整数。
浮点值精确度最高达到17位小数,小数点后6个0浮点值会转为科学计数法(0.0000003 记为3e-7)。
let a = 0.1;
let b = 0.2;
console.log(a+b);//0.30000000000000004 存在舍入错误
3.5.2 值范围
ECMAScript 最大值Number.MAX_VALUE,最小值是Number.MIN_VALUE(其最小值也是大于0的,接近0。 )
MIN_VALUE 的值约为 5e-324。小于 MIN_VALUE ("underflow values") 的值将会转换为 0。它不是负值。
超出的范围自动转换为Infinity值,正无穷和负无穷。
javascript最大存储空间是Number.MAX_VALUE = 2^1024,超过这个范围会被自动转成Infinity 。
范围在-2^53~2^53之间的数字能够精准表示
可以通过isFinite()来判断是否有限大。
3.5.3 NaN
not a number , 在0/0(这里+0和-0都可以),或者用"a"/0 会返回NaN。
其他值除+-0会返回+-Infinity 正负无穷。(5/0 or 5/-0); 不会出现除0异常。
isNaN()可以用来判断是否数值,可以传入任意类型。
console.log(isNaN(NaN));// true
console.log(isNaN(10));// false
console.log(isNaN("10"));// false
console.log(isNaN("blue"));// true 不可以转成数值
console.log(isNaN(true));// false
let message = true;
let messageNumber = Number(message);
console.log(messageNumber); // 1
3.5.4 数值转换
Number() 可以使用任意类型的数据。
parseInt()、parseFloat() 主要用于字符串转换为数值。比如:“123blue"转换成123;
Number可以将true转为1 false转为0,null返回0, undefined返回NaN.
parseInt("xxx",2) 第二个参数表示进制。
parseFloat(”0xA") 的十六进制转换始终返回0;
3.6 字符串
可以使用“” '' ``来表示。
toString()可以用于数值、布尔值、对象和字符串值,null和undefined值没有toString() 方法。
如果不确定一个值是不是null或者undefined,可以先用String()转换成字符串,null会转成“null",undefined转换成”undefined“。
与使用单双引号不同,反引号`可以保留换行空格。
let lineTemp= `hello2
world2`;
console.log(lineTemp); // hello2
// world2
字符串插值:${xxx} 必须搭配``使用。
xxx 可以是变量,可以是”xxx" 字符串,可以是函数和方法。
let value = 45;
let lineTemp= "${value}";
console.log(lineTemp); // ${value}
let lineTemp2= `${value}`;
console.log(lineTemp2); // 45
let lineTemp3= String.raw`\u00A9`;
console.log(lineTemp3); // \u00A9
let lineTemp4= `\u00A9`;
console.log(lineTemp4); // ©
String.raw可以让空行或者Unicode字符原样输出。
模板字面量搭配标签函数
let a = 1,b =2;
function simpleTag(strings,expressA,expressB,sumExpress){
console.log(expressA); // 1
console.log(expressB); // 2
console.log(sumExpress); // 3
console.log(strings); // ["", "+", "=", "", raw: Array(4)]
return "foo";
}
let tagResult = simpleTag`${a}+${b}=${a+b}`;
console.log(tagResult);// foo
// expressA,expressB,sumExpress 可以用可变参数...express替换。
3.7 Symbol类型(重点)
符号用来创建唯一记号,进而用作非字符串形式的对象属性。
3.7.1 符号/全局符号创建
let msg = Symbol();
let msg2 = Symbol("foo");
let golbalSymbol = Symbol.for("foo");
let key = Symbol.keyFor(golbalSymbol);
let key2 = Symbol.keyFor(msg2);
console.log(key); // foo
console.log(key2);// undefined
let msg3 = Symbol.keyFor(123); // typeError
1、不能用new Symbol()创建Symbol,因为Symbol是原始数据类型,用了new一般就成了它的包装类型,且new Symbol()这样调用也是不允许的。
2、Symbol.for用来创建全局符号,在运行时不同部分需要共享或者重用符号实例都是可以的。多次调用Symbol.for("foo")取到的是同一个对象,所以全局符号是唯一的。
3、能用字符串或者数值作为属性的地方,都可以使用符号。
3.7.2 符号作为属性
let s1 = Symbol("foo");
let s2 = Symbol('foo2');
let s3 = Symbol('foo3');
let o = {
s2:"ssss",
[s1]:"111"
}
console.log(o.s2); // ssss
console.log(o[s1]);// 111
console.log(Object.getOwnPropertyDescriptors(o));// {s2: {…}, Symbol(foo): {…}}
console.log(Object.getOwnPropertyNames(o));// ["s2"]
console.log(Object.getOwnPropertySymbols(o));// [Symbol(foo)]
console.log(Reflect.ownKeys(o));// (2) ["s2", Symbol(foo)]
Object.defineProperty(o,s3,{value:"xxxx"});
console.log(Object.getOwnPropertyDescriptors(o));// {s2: {…}, Symbol(foo): {…}, Symbol(foo2): {…}}
console.log(Object.getOwnPropertyNames(o));// ["s2"]
console.log(Object.getOwnPropertySymbols(o));//(2) [Symbol(foo), Symbol(foo3)]
console.log(Reflect.ownKeys(o));// (3) ["s2", Symbol(foo), Symbol(foo3)]
Object.defineProperties(o,{
s5:{value:"yyyy"},
s6:{value:"zzzz"}
})
console.log(Object.getOwnPropertyDescriptors(o));// {s2: {…}, s5: {…}, s6: {…}, Symbol(foo): {…}, Symbol(foo3): {…}}
console.log(Object.getOwnPropertyNames(o));// ["s2", "s5", "s6"]
console.log(Object.getOwnPropertySymbols(o));// [Symbol(foo), Symbol(foo3)]
console.log(Reflect.ownKeys(o));// (5) ["s2", "s5", "s6", Symbol(foo), Symbol(foo3)]
要想成为o的符号属性,必须使用[]或者Object.defineProperty 或者Object.defineProperties 带[] ,否则是常规属性。
Object.getOwnPropertyNames() 获取实例常规属性数组
Object.getOwnPropertySymbols() 获取实例符号属性数组
Object.getOwnPropertyDescriptors() 返回包含常规和符号属性描述符的对象。
3.7.3 常用内置符号
@@interator 就是Symbol.iterator。
1、Symbol.asyncIterator 异步生成器 由for-await-of语句使用。
class Emitter{
constructor(max){
this.max = max;
this.index = 0;
}
async *[Symbol.asyncIterator](){
while(this.index<this.max){
yield new Promise((resolve)=> resolve(this.index++));
}
}
}
async function asyncCount(){
let emitter = new Emitter(5);
for await(const x of emitter){
console.log(x);
}}
asyncCount(); // 0 1 2 3 4
2、Symbol.hasInstance
主要使用:类名or方法名Symbol.hasInstance 等同于 xxx instanceof 类名or方法名。
function Foo(){}
let f = new Foo();
class Bar{}
let b = new Bar();
console.log(Foo[Symbol.hasInstance](f)); // true
console.log(Bar[Symbol.hasInstance](b));// true
继承的子类想改写Symbol.hasInstance的值,可以通过static Symbol.hasInstance方法改写。
3、Symbol.isConcatSpreadable
用来追加元素时,是否可以进行打平操作。
数组对象调用concat(另一个数组)默认会打平,如果设置xx[Symbol.isConcatSpreadable]=false或者假值,则不打平。
类数组对象比如Object等默认不打平,设置为true才会打平。
其他不是类数组对象等则即使设置了xx[Symbol.isConcatSpreadable]=true也会忽略,concat不上去。
4、Symbol.iterator
返回对象默认的迭代器,由for-of语句使用,跟前面Symbol.asyncIterator类似。
5、Symbol.match
let reg = /foo/;
console.log("foobar".match(reg)); // ["foo", index: 0, input: "foobar", groups: undefined]
let reg2 = "foo";
console.log("foobar".match(reg2)); // ["foo", index: 0, input: "foobar", groups: undefined]
let reg3 = 123;
console.log("foo123".match(reg3)); // ["123", index: 3, input: "foo123", groups: undefined]
class StringBuilder {
constructor(str) {
this.str = str;
}
[Symbol.match](target) {
return target.includes(this.str);
}
}
console.log("foobar".match(new StringBuilder("bar")));// true
字符串才有此方法,默认调用String.prototype.match()返回Object,可以通过重写[Symbol.match](target)修改返回值。
Symbol.replace 与 Symbol.search、Symbol.split跟Symbol.match类似,都是用的String.prototype的函数,前者返回替换后的字符串,后者返回搜索到的index,同样可以去重写。
6、Symbol.species
这个属性表示一个函数值,作为创建派生对象的构造函数。
class Baz extends Array{
static get [Symbol.species](){
// return this; // 默认
return Array;
}
}
let baz = new Baz();
console.log(baz instanceof Array);// true
console.log(baz instanceof Baz);// true
baz = baz.concat('baz');
console.log(baz instanceof Array);// true
console.log(baz instanceof Baz); // false
默认Symbol.species返回的Baz类本身,如果修改了返回值类型,那么通过concat或者map等操作返回的派生类对象就是 Array类型,不再是Baz类型了。
7、Symbol.toPrimitive
强制将对象转换为原始值,包括字符串、数值和未指定的原始类型。
class Bar{
[Symbol.toPrimitive](hint) {
switch(hint) {
case "number":
return 3;
case "string":
return "string bar";
case 'default':
default:
return "default bar";
}
}
}
let bar = new Bar();
console.log( bar + 3); // default bar3
console.log(3 - bar); // 0
为啥bar+3识别不出bar要强转为3呢,学C++学的不像。
8、Symbol.toStringTag
class Bar{
}
let bar = new Bar();
bar[Symbol.toStringTag] = 'bar';
console.log(bar[Symbol.toStringTag]);// bar
9、Symbol.unscopables
一般不建议使用这个,主要是跟with搭配使用,with环境绑定的上下文中可以排除某个属性用。
let o = {foo :'bar'};
with(o){
console.log(foo);// bar
}
o[Symbol.unscopables] = {
foo:true
}
with(o){
console.log(foo);// ReferenceError
}
幸好,kotlin没有这么无聊的功能,用with 很安全。
3.8 Object类型
类似于java中的Object类,是派生其他对象的基类。
主要属性和方法有:
interface Object {
constructor: Function;
toString(): string;
toLocaleString(): string;
valueOf(): Object;
hasOwnProperty(v: PropertyKey): boolean;
isPrototypeOf(v: Object): boolean;
propertyIsEnumerable(v: PropertyKey): boolean;
}
4.操作符
1、操作符这块大部分跟其他语言差不多,不过有些特殊的自动强转。
let s ="2cc";
let b = false;
let f = 1.1;
let o = {
valueOf(){
return -1;
}
}
console.log(++s); // NaN
console.log(++b); // 1
console.log(--f); // 0.10000000000000009
console.log(--o);// -2
字符串转不了数值进行递增递减则NaN,布尔类型的强转成0 or 1再计算,对象类型的调用valueOf再计算。
2、逻辑非运算中调用双!相当于调用了Boolean()转换。
console.log(!!"blue");// true
console.log(!!"");// false
3、逻辑或用于赋值,这个常用到
let a = "11";
let b = null;
let c = b || a;
console.log(c); // 11
let a = "";
let b = undefined;
let c = a || b;
console.log(c); // undefined
let d = b || a;
console.log(d); // ""
|| 左边的b是优先选择,如果不为null则就是b,如果b为null,则选择右边的a。 undefined和”“是不分伯仲。
4、不会存在除0异常。
console.log(3/0); // Infinity
5、指数操作符是**,等同于Math.pow(a,b);
6、NaN是个很诡异的存在,跟数值比大小,始终是false。
let a = "a";
let s = -a;
console.log(s);// NaN
console.log(s > 3); // false
console.log(s <= 3);// false
7、等于和不等于
console.log(null == undefined);// true
console.log(NaN == NaN); // false
console.log(null == 0);// false
console.log(undefined == 0); // false
console.log("" == 0);// true
console.log("" == null);// false
console.log("" == undefined);// false
console.log(false == 0); // true
null和undefined相等,在比较时,不能转换为其他类型的值进行比较。
全等则是不做类型转换,数据类型不同则值为false
console.log("5" == 5);// true
console.log("5" === 5); // false
8、for循环也支持label语句