《JavaScript 高级程序设计》第三章 语法基础

423 阅读11分钟

一、语法

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
nullundefined相等,在比较时,不能转换为其他类型的值进行比较。
全等则是不做类型转换,数据类型不同则值为false
console.log("5" == 5);// true
console.log("5" === 5); // false

8、for循环也支持label语句