JavaScript基础巩固系列——入门篇+数据类型

全手打原创,转载请标明出处:https://www.cnblogs.com/dreamsqin/p/13686263.html, 多谢,=。=~(如果对你有帮助的话请帮我点个赞啦)

重新学习JavaScript是因为当年转前端有点儿赶鸭子上架的意味,我一直在反思我的知识点总是很零散,不能在脑海中形成一个完整的体系,所以这次想通过再次学习将知识点都串联起来,结合日常开发的项目,达到温故而知新的效果。与此同时,总结一下我认为很重要但又被我遗漏的知识点~

背景知识

JavaScript既是一种轻量级的脚本语言,又是一种嵌入式语言。

  • 脚本语言(script language):不具备开发操作系统的能力,只用来编写控制大型应用程序(例如浏览器)的脚本。
  • 嵌入式语言(embedded):通过嵌入大型应用程序调用宿主环境提供的底层API(例如浏览器为JavaScript提供浏览器控制类、DOM类、Web类API;Node为JavaScript提供文件操作、网络通信等API;)实现本身核心语法不支持的复杂功能。

JavaScript历史

  • 为什么诞生:Navigator 浏览器需要一种可以嵌入网页的脚本语言,用来控制浏览器行为,因为当时网速很慢而且上网费很贵,有些操作不宜在服务器端完成。
  • 需求:不需要太强的功能,语法较简单,容易学习和部署。

JavaScript的编程风格是函数式编程和面向对象编程的一种混合体。

  • 语法来源
    • 基本语法:借鉴 C 语言和 Java 语言。
    • 数据结构:借鉴 Java 语言,包括将值分成原始值和对象两大类。
    • 函数的用法:借鉴 Scheme 语言和 Awk 语言,将函数当作第一等公民,并引入闭包。
    • 原型继承模型:借鉴 Self 语言(Smalltalk 的一种变种)。
    • 正则表达式:借鉴 Perl 语言。
    • 字符串和数组处理:借鉴 Python 语言。

ECMAScript历史

ECMAScript 和 JavaScript 的关系是,前者是后者的规范,后者是前者的一种实现。

  • 为什么诞生:微软开发了JScript并内置于IE3.0浏览器中,Netscape 面临丧失浏览器脚本语言的主导权局面,最终决定将 JavaScript 提交给国际标准化组织 ECMA(European Computer Manufacturers Association),希望 JavaScript 能够成为国际标准,以此抵抗微软。
  • 需求:规定浏览器脚本语言的标准。

基本语法

  • 变量->变量提升:JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有变量的声明语句,都会被提升到代码的头部,对应的也有函数名提升(前提是使用function命令声明,如果采用赋值语句定义就会报错)。
// 原代码(如果没有变量提升会报错:a is not defined)
console.log(a);
var a = 1;

// 实际运行代码
var a;
console.log(a);
a = 1;

// 运行不会报错
f();
function f() {}
  • 标识符:用来识别各种值(变量、函数)的合法名称,只能以任意 Unicode 字母、美元符号$或下划线_开头,只能包含 Unicode 字母、美元符号、下划线及数字。
    • 中文是合法的标识符,可以用作变量名。
    • JavaScript保留字:arguments、break、case、catch、class、const、continue、debugger、default、delete、do、else、enum、eval、export、extends、false、finally、for、function、if、implements、import、in、instanceof、interface、let、new、null、package、private、protected、public、return、static、super、switch、this、throw、true、try、typeof、var、void、while、with、yield。
  • 注释:JavaScript可以兼容HTML代码注释,所以<!---->也被视为合法的单行注释,但-->只有在行首才会被当作单行注释,否则为正常运算,例如:
function countdown(n) {
  while (n --> 0) console.log(n);    // 被作为n-- > 0执行
}
countdown(3)
// 2
// 1
// 0
  • 区块:使用大括号,将多个相关的语句组合在一起。
    • 区块对于var命令来说不构成单独的作用域,与不使用区块的情况没有任何区别。例如:
    {
      var a = 1;
    }
    a // 1,在区块外部变量依然有效
    
  • 条件语句
    • 终于明白条件判断大家为什么要这么写了if (2 = x),因为常量写在运算符的左边,一旦不小心将相等运算符写成赋值运算符,就会报错,因为常量不能被赋值。
    • 在没有标明区块(大括号)时,else代码块总是与离自己最近的那个if语句配对。
    • switch语句后面的表达式与case语句后面的表示式比较时,采用的是严格相等运算符(===),即不会发生类型转换。
  • 循环语句
    • break语句用于跳出代码块或循环。
    • continue语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环。
    • 标签:JavaScript 语言允许语句的前面有标签(label),相当于定位符,用于跳转到程序的任意位置,例如:
    top:
      for (var i = 0; i < 3; i++){
        for (var j = 0; j < 3; j++){
          if (i === 1 && j === 1) break top;
          console.log('i=' + i + ', j=' + j);
        }
      }
    // i=0, j=0
    // i=0, j=1
    // i=0, j=2
    // i=1, j=0
    

数据类型

  • 整数和浮点数:
    • JavaScript内部,所有数字都是以64位浮点数形式储存。
    1 === 1.0 // true
    
    • 由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。
    0.1 + 0.2 === 0.3 // false
    0.3 / 0.1 // 2.9999999999999996
    (0.3 - 0.2) === (0.2 - 0.1) // false
    
  • 数值精度:精度最多只能到53个二进制位,绝对值小于2的53次方的整数,即负的2的53次方到 2的53次方,都可以精确表示。
Math.pow(2, 53) // 9007199254740992(简单的法则就是JavaScript对15位的十进制数都可以精确处理)
  • 数值范围:JavaScript 提供Number对象的MAX_VALUEMIN_VALUE属性,返回可以表示的最大值和最小值,负向溢出时返回0,正向溢出时返回Infinity。
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324
  • 数值表示法:0b11(二进制)、0o377(八进制)、35(十进制)、0xFF(十六进制)、123e3(科学计数法)
    • 小数点前的数字多于21位、小数点后的零多于5个时JavaScript自动将数值转为科学计数法表示。
    • 默认情况下,JavaScript 内部会自动将八进制、十六进制、二进制转为十进制。
  • 特殊数值:
    • 几乎所有场合,正零负零都会被当作正常的0,是等价的,唯一有区别的场合是+0或-0当作分母,返回的值是不相等的。
    (1 / +0) === (1 / -0) // false(除以正零得到+Infinity,除以负零得到-Infinity)
    
    • NaN表示“非数字”(Not a Number),主要出现在将字符串解析成数字出错的场合、还有一些数学函数的运算结果,数据类型属于Number,不等于任何值(包括它本身),布尔运算时被当作false,和任何数运算得到的都是NaN。
    typeof NaN // 'number'
    NaN === NaN // false 
    Boolean(NaN) // false
    NaN + 32 // NaN
    
    • Infinity表示“无穷”,用来表示两种场景,一种是一个正的数值太大,或一个负的数值太小,无法表示;另一种是非0数值除以0,Infinity有正负之分,大于一切数值(除了NaN),四则运算符合无穷的数学计算规则,与null计算时null会转成0,与undefined计算返回的都是NaN。
    Infinity === -Infinity // false
    Infinity > 1000 // true
    Infinity > NaN // false
    0 * Infinity // NaN(特殊)
    Infinity - Infinity // NaN(特殊)
    Infinity / Infinity // NaN(特殊)
    0 / Infinity // 0
    Infinity / 0 // Infinity
    
  • 全局方法:
    • parseInt():用于将字符串转为整数,头部有空格会自动去除,转换时是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分,如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外)返回NaN,可以接受第二个参数(2到36之间,超出返回NaN,为0、undefined、null则忽略)表示被解析的值的进制并返回该值对应的十进制数。
    parseInt('15e2') // 15
    parseInt('.3') // NaN
    parseInt('+1') // 1
    parseInt('') // NaN
    parseInt('1000', 2) // 8
    parseInt('10', null) // 10
    
    • parseFloat():用于将一个字符串转为浮点数,需要与Number()函数区分。
    parseFloat('') // NaN
    Number('') // 0
    parseFloat('123.45#') // 123.45
    Number('123.45#') // NaN
    
    • isNaN():用来判断一个值是否为NaN,只对数值有效,如果传入其他值,会被先转成数值(所以重点关注里面传入的值是否可以被Number转为数值)。
    isNaN('Hello') // true
    // 相当于
    isNaN(Number('Hello')) // true
    //判断NaN更可靠的方法是,利用NaN是唯一一个不等于自身的这个特点
    function myIsNaN(value) {
      return value !== value;
    }
    
    • isFinite():返回一个布尔值,表示某个值是否为正常的数值,除了Infinity、-Infinity、NaN和undefined这几个值会返回false,isFinite对于其他的数值都会返回true。
  • 字符串:
    • 字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始),但无法改变字符串之中的单个字符。
    'hello'[1] // "e"
    
    var s = 'hello';
    delete s[0];
    s // "hello"
    s[1] = 'a';
    s // "hello"
    
    • 对于码点在U+10000到U+10FFFF之间的字符,JavaScript 总是认为它们是两个字符(length属性为2),所以JavaScript 返回的字符串长度可能是不正确的。
    • Base64转码:就是一种编码方法,可以将任意值转成 0~9、A~Z、a-z、+和/这64个字符组成的可打印字符,应用场景一个是将不可打印符号转成可打印字符(例如ASCII的0~31),或者以文本格式传递二进制数据,有两个原生方法:btoa()任意值转为 Base64 编码、atob()Base64 编码转为原来的值,但不适用于非ASCII的字符(需要先通过encodeURIComponent进行转码)。
    var string = 'Hello World!';
    btoa(string) // "SGVsbG8gV29ybGQh"
    atob('SGVsbG8gV29ybGQh') // "Hello World!"
    
    function b64Encode(str) {
      return btoa(encodeURIComponent(str));
    }
    function b64Decode(str) {
      return decodeURIComponent(atob(str));
    }
    b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
    b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"
    
  • 对象:
    • 对于不符合标识名条件的键名必须加上引号,例如第一个字符为数字,或者含有空格或运算符。
    var obj = {
     '1p': 'Hello World',
     'h w': 'Hello World',
     'p+q': 'Hello World'
    };
    
    • JavaScript 引擎如果遇到无法确定是对象还是代码块的情况,一律解释为代码块,如果要解释为对象,最好在大括号前加上圆括号。
    // eval语句(作用是对字符串求值)
    eval('{foo: 123}') // 123
    eval('({foo: 123})') // {foo: 123}
    
    • 数字键在方括号读取运算符中可以不加引号,但不能使用点运算符读取。
    var obj = {
      123: 'hello world'
    };
    
    obj.123 // 报错
    obj[123] // "hello world"
    
    • Object.keys():返回一个对象本身的所有属性。
    var obj = {
      key1: 1,
      key2: 2
    };
    
    Object.keys(obj);
    // ['key1', 'key2']
    
    • delete:用于删除对象的属性,删除成功后返回true,但删除一个不存在的属性,delete不报错,而且也返回true,只有某属性不可删除时(configurable:false)才返回false,只能删除对象本身的属性,无法删除继承的属性。
    • in:用于检查对象是否包含某个属性,如果包含就返回true,否则返回false,但无法识别哪些属性是对象自身的,哪些属性是继承的(可以用hasOwnProperty判断)。
    var obj = { p: 1 };
    'p' in obj // true
    'toString' in obj // true
    
    var obj = {};
    if ('toString' in obj) {
      console.log(obj.hasOwnProperty('toString')) // false
    }
    
    • for...in:用来遍历一个对象的全部属性,仅遍历可遍历(enumerable:true)属性跳过不可遍历属性,不仅遍历对象自身的属性,还遍历继承的属性(前提是继承的属性是可遍历的),如果想仅遍历自身属性可用hasOwnProperty在循环内部判断。
    var obj = {a: 1, b: 2, c: 3};
    for (var i in obj) {
      console.log('键名:', i);
      console.log('键值:', obj[i]);
    }
    // 键名: a
    // 键值: 1
    // 键名: b
    // 键值: 2
    // 键名: c
    // 键值: 3
    
    • with:操作同一个对象的多个属性时,提供一些书写的方便(在内部可以不使用点运算符就能直接读取属性),如果with区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量,不建议使用with语句,无法判断内部变量为对象属性还是全局变量
    var obj = {
      p1: 1,
      p2: 2,
    };
    with (obj) {
      p1 = 4;
      p2 = 5;
    }
    // 等同于
    obj.p1 = 4;
    obj.p2 = 5;
    
  • 函数:
    • name属性:返回函数的名字,常用的是获取参数函数的名字。
    var myFunc = function () {};
    function test(f) {
      console.log(f.name);
    }
    test(myFunc) // myFunc
    
    • length属性:返回函数预期传入的参数个数,即函数定义之中的参数个数。
    function f(a, b) {}
    f.length // 2
    
    • 函数本身作用域:函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
    var a = 1;
    var x = function () {
      console.log(a);
    };
    
    function f() {
      var a = 2;
      x();
    }
    
    f() // 1
    
    • 参数传递方式:如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value),在函数体内修改参数值,不会影响到函数外部;如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference),在函数内部修改参数,将会影响到原始值,但如果修改参数的地址指向,则不会影响原始值。
    var obj = { p: 1 };
    function f(o) {
      o.p = 2;
    }
    f(obj);
    obj.p // 2
    
    function f2(o) {
      o = { p: 3 };
    }
    f2(obj);
    obj.p // 2
    
    • arguments对象:包含了函数运行时的所有参数,这个对象只有在函数体内部才可以使用,正常模式下arguments对象可以在运行时修改,但严格模式下不行,它很像数组,但它是一个对象,数组专有的方法(比如sliceforEach)无法使用,但可以将它转为真正的数组(slice方法或遍历填入新数组)。
    var f = function(a, b) {
      'use strict'; // 开启严格模式
      arguments[0] = 3;
      arguments[1] = 2;
      return a + b;
    }
    f(1, 1) // 2
    
    var args = Array.prototype.slice.call(arguments);
    // 或者
    var args = [];
    for (var i = 0; i < arguments.length; i++) {
      args.push(arguments[i]);
    }
    
    • 闭包:能够读取其他函数内部变量的函数,由于在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”,闭包的最大用处,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在,还有一个是封装对象的私有属性和私有方法。
      PS:外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。
    function createIncrementor(start) {
      return function () {
        return start++;
      };
    }
    var inc = createIncrementor(5);
    
    inc() // 5
    inc() // 6
    inc() // 7
    
    • 立即调用的函数表达式(IIFE-Immediately-Invoked Function Expression):通常情况下,只对匿名函数使用,它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
    (function(){ /* code */ }());
    // 或者
    (function(){ /* code */ })();
    
    • eval命令:接受一个字符串作为参数,并将这个字符串当作语句执行,参数如果不是字符串就会原样返回,eval没有自己的作用域,可能会因为修改当前作用域的变量造成安全问题(使用严格模式,eval内部声明的变量,不会影响到外部作用域,但依然可以读写当前作用域的变量),最常见的场合是解析 JSON 数据的字符串(应使用JSON.parse替代),凡是使用别名执行eval,eval内部一律是全局作用域。
        eval('var a = 1;'); // 生成变量a
        eval(123) // 123 // 非字符串原样返回
    
  • 数组:
    • JavaScript 语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串,之所以可以用数值读取,是因为非字符串的键名会被转为字符串。
    var arr = ['a', 'b', 'c'];
    arr['0'] // 'a'
    arr[0] // 'a'
    
    • JavaScript 使用一个32位整数保存数组的元素个数,所以数组成员最多只有 4294967295 个(232 - 1)个,length属性的最大值就是 4294967295。
    • length属性可写,如果设置一个小于当前成员个数的值,该数组的成员数量会自动减少到length设置的值,可以通过将length设置为0来清空数组。
    var arr = [ 'a', 'b', 'c' ];
    arr.length = 2;
    arr // ["a", "b"]
    arr.length = 0;
    arr // []
    
    • 数组本质上是一种对象,所以可以为数组添加属性,但是这不影响length属性的值。
    var a = [];
    a['p'] = 'abc';
    a.length // 0
    
    • for...in:不仅会遍历数组所有的数字键,还会遍历非数字键,所以不推荐使用它对数组进行遍历,应该使用循环遍历或forEach方法替代。
    var a = [1, 2, 3];
    a.foo = true;
    for (var key in a) {
      console.log(a[key]);
    }
    // 1
    // 2
    // 3
    // true
    
    a.forEach(function (item) {
      console.log(item);
    });
    // 1
    // 2
    // 3
    
    • delete:删除数组元素后不影响length属性,删除的位置形成空位,读取时返回undefined,所以用length属性遍历时需要注意,是无法跳过空位的。
    var a = [, , ,];
    a[1] // undefined(空位与该位置值为undefined不一样,空位在forEach、for...in、Object.keys遍历时会跳过
    
    • 类数组对象(array-like object):很像数组的对象,如果一个对象的所有键名都是正整数或零,并且有length属性(非动态,不会随着成员的变化而变化),不具备数组的特有方法,常见的有arguments对象、大多数Dom元素集、字符串,slice方法可以将类数组对象转换为真正的数组,除此之外可以通过call()把数组方法嫁接到对象上。
    var obj = {
      0: 'a',
      1: 'b',
      2: 'c',
      length: 3
    };
    
    obj[3] = 'd';
    obj.length // 3
    
    // forEach 方法(本来arguments对象无法调用该方法,但这种方法比直接使用数组原生的forEach要慢,所以最好还是先将“类似数组的对象”转为真正的数组,然后再直接调用数组的forEach方法。)
    function logArgs() {
      Array.prototype.forEach.call(arguments, function (elem, i) {
        console.log(i + '. ' + elem);
      });
    }
    

参考资料

JavaScript 语言入门教程 :https://wangdoc.com/javascript/index.html

posted @ 2020-09-17 17:13  Dreamsqin  阅读(152)  评论(0编辑  收藏  举报