zoukankan      html  css  js  c++  java
  • [js]变量与数据类型篇

    一、变量

      在JavaScript中就用一个变量名表示变量,变量名是大小写英文、数字、$_的组合,不能用数字开头。变量名也不能是JavaScript的关键字;

    1、变量的声明

      (1)var:申明一个变量,用=对变量进行赋值。可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量,但是要注意只能用var申明一次;

       ES6中新增let和const

      (2)let:所声明的变量,只在let命令所在的代码块内有效;必须先声明,不存在变量提升;不允许在相同作用域内,重复声明同一个变量;

      (3)const声明一个只读的常量。一旦声明,常量的值就不能改变;只声明不赋值,就会报错。

    2、变量作用域

      (1)全局变量:声明在函数外部的变量;浏览器环境中全局变量为window属性;

      (2)函数作用域:针对局部变量来说的,在函数中定义的变量在函数外不能获取;

      (3)块级作用域(ES6):

    二、数据类型

      6种基本类型

    • 数值(number):整数和小数(比如13.14
    • 字符串(string):文本(比如Hello World)。
    • 布尔值(boolean):表示真伪的两个特殊值,即true(真)和false(假)
    • undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值
    • null:表示空值,即此处的值为空。
    • 对象(object):各种值组成的集合。

    <原始类型>

    1、字符串(string):

      1.1  字符串

    • 字符串是以单引号'或双引号"括起来的任意文本
    • ASCII字符可以以x##形式的十六进制表示
    • JavaScript 允许采用uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。(常用于字符图标)

      1.2 字符串常用方法及属性

      (1)获取字符长度:.length

      (2)获取第n位字符  .charAt(n)

      (3)获取第n位字符编码 .charCodeAt(n)

      (4)拼接字符串  a.concat('123','abc')

      (5)获取、搜索字符串位置方法 str.indexOf('a')   str.lastIndexOf('a')    找不到时返回-1

      (6)删除字符串前后的空格 str.trim()

      (7)截取字符串操作方法

      •  str.slice(start,end);           两个参数可正可负,负值代表从右截取,返回值:[start,end] 也就是说返回从start到end-1的字符
      •  str.substring(start,end);  两个参数都为正数,返回值:[start,end) 也就是说返回从start到end-1的字符
      •  str.substr(start,length);     第一个参数指定子字符串开始位置,第二个参数表示返回的字符个数

      (8)字符串大小写转换方法

      • str.toLowerCase();
      • str.toUpperCase();

      (9)字符串分割成字符串数组   str.split(separator,limit); separator指定字符串或正则,limit指定数组的最大长度 ;str.split("");  每个字符都被分割 ;数组变成字符串arr.join('-')

      (10)字符串模式匹配方法

      • str.match(rgExp);  
      • str.test(rgExp);    

      (11)替换第一个子字符串  str.replace("at","one")

      1.3字符串模板语法  

      反引号(``)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量;

      模板字符串中嵌入变量,需要将变量名写在${}之中

     2、数值(Number)

      2.1 数值 

    •  JavaScript不区分整数和浮点数,统一用Number表示 ;
    •  在JavaScript的内部采用IEEE754格式来表示数字,所以不区分整数和浮点数,都是用64位浮点数的形式储存。就是说,在JavaScript内部,就根本没有小数。但是有些运算必须得需要整数完成,所以JavaScript有时会把64位的浮点数转换成32位的整数,再进行运算。  
    123; // 整数123
    0.456; // 浮点数0.456
    1.2345e3; // 科学计数法表示1.2345x1000,等同于1234.5
    -99; // 负数
    NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
    Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity

      2.2 表示方法

       2.2.1 整数
    • 二进制:有前缀0b的数值,出现0,1以外的数字会报错
    • 八进制:有前缀0o的数值,或者是以0后面再跟一个数字(0-7)。如果超出了前面所述的数值范围,则会忽略第一个数字0,视为十进制数
    •     注意:八进制字面量在严格模式下是无效的,会导致支持该模式的JavaScript引擎抛出错误 
    • 十六进制:有前缀0x,后跟任何十六进制数字(0~9及A~F),字母大小写都可以,超出范围会报错
    • 十进制

      ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示

    0b111110111 === 503 // true
    0o767 === 503 // true
      2.2.2 浮点数

      所谓浮点数,就是该数值中必须包含一个小数点,并且小数点后面必须至少有一位数字。与整数不同,浮点数只能用十进制来表示

      浮点数的精度远远不如整数,所以设计浮点数的运算好比较要小心

    console.log(0.1 + 0.2 == 0.3); //false 0.30000000000000004
    console.log(0.6/0.2); //2.9999999999999996
      2.2.3科学计数法

      对于那些极大极小的数值,可以用e表示法(即科学计数法)表示的浮点数值表示。用e表示法表示的数值等于e前面的数值乘以10的指数次幂

      以下两种情况,JavaScript会自动将数值转为科学计数法表示,其他情况都采用字面形式直接表示。

    • 小数点前的数字多余21位
     console.log(1234567890123456789012);// 1.2345678901234568e+21
     console.log(123456789012365648787); //123456789012365660000
    • 小数点后面的0多余5位
    console.log(0.0000006);//6e-7
    console.log(0.000006); //0.000006

      2.3 范围 

    • 由于内存的限制,ECMAScript并不能保存世界上所有的数值,所以就有了最大值和最小值
    • 最小值保存在Number.MIN_VALUE中,这个值是5e-324
    • 最大值保存在Number.MAX_VALUE,这个值是1.7976931348623157e+308
    console.log(Number.MIN_VALUE) //5e-324
    console.log(Number.MAX_VALUE); //1.7976931348623157e+308
    • 如果数字超过最大值,javascript会返回Infinity,这称为正向溢出(overflow);
    • 如果等于或超过最小负值-1023(即非常接近0),javascript会直接把这个数转为0,这称为负向溢出(underflow)
    • 如果要想确定一个数值是不是有穷的,可以使用isFinite()函数。这个函数在参数位于最小与最大数值之间时会返回true
    var result = Number.MAX_VALUE + Number.MAX_VALUE;
    console.log(isFinite(result)); //false

      2.4 常见问题

    • typeof NaN // 'number'  ---NaN不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于Number
    • NaN === NaN // false   ---NaN不等于任何值,包括它本身
    • (1 / +0) === (1 / -0) // false  ---除以正零得到+Infinity,除以负零得到-Infinity,这两者是不相等的

      2.4 数值常用方法

      parseInt('123',10)  用于将字符串转为整数,方法还可以接受第二个参数(2到36之间),表示被解析的值的进制

      parseFloat('314e-2') 用于将一个字符串转为浮点数。

      isNaN(123)      用于方法可以用来判断一个值是否为NaN

    3、布尔值(boolean)

      (1)布尔值代表“真”和“假”两个状态。“真”用关键字true表示,“假”用关键字false表示。布尔值只有这两个值。

      (2)以下运算返回布尔值:

      •   两元逻辑运算符: && (And),|| (Or)
      •   前置逻辑运算符: ! (Not)
      •   相等运算符:===!====!=
      •   比较运算符:>>=<<=

      (3)下面六个值被转为false,其他值都视为true

      •   undefined  
      •   null  
      •   false  
      •   0  
      •   NaN  
      •   ""''(空字符串)

    <合成类型>

    4、对象(object)

      4.1对象基本概念

    •  合成类型(complex type)
    •  对象(object)是 JavaScript 语言的核心概念,也是最重要的数据类型;
    •  对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合;
    •  JavaScript 规定,如果行首是大括号,一律解释为语句(即代码块)。如果要解释为表达式(即对象),必须在大括号前加上圆括号。

      4.2键名

    •  对象的所有键名都是字符串(ES6 又引入了 Symbol 值也可以作为键值),所以加不加引号都可以;
    •  如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),且也不是数字,则必须加上引号,否则会报错;
    • 对象的属性之间用逗号分隔,最后一个属性后面可以加逗号(trailing comma),也可以不加;

      4.3对象的引用

      如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。

      4.4对象的方法

    • Object.keys查看一个对象本身的所有属性
    • delete命令用于删除对象的属性,删除成功后返回true;只有一种情况,delete命令会返回false,那就是该属性存在,且不得删除(Object.defineProperty);delete命令只能删除对象本身的属性,无法删除继承的属性
    • in运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true,否则返回false;in运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。
    • for...in循环用来遍历一个对象的全部属性
      • 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
      • 它不仅遍历对象自身的属性,还遍历继承的属性。
      • hasOwnProperty方法,在循环内部判断一下,某个属性是否为对象自身的属性。
    • with语句用于操作同一个对象的多个属性时,提供一些书写的方便
    var obj = {p1: 1, p2: 2,};
    with (obj) {
      p1 = 4;
      p2 = 5;
    }

    5、数组(object)

      5.1 数组的基本概念

    • 数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示;
    • 本质上,数组属于一种特殊的对象。typeof运算符会返回数组的类型是object;
    • 当数组的某个位置是空元素,即两个逗号之间没有任何值,我们称该数组存在空位(hole)   ;
    • 数组的空位是可以读取的,返回undefined;
    • 数组的某个位置是空位,与某个位置是undefined,是不一样的。如果是空位,使用数组的forEach方法、for...in结构、以及Object.keys方法进行遍历,空位都会被跳过

      5.2 数组的构造方法

       Array是 JavaScript 的原生对象,同时也是一个构造函数,可以用它生成新的数组。   

    var arr = new Array(2);

      Array构造函数对于不同的参数,返回不一样的结果

    // 无参数时,返回一个空数组
    new Array() // []
    // 单个正整数参数,表示返回的新数组的长度
    new Array(1) // [ empty ]
    // 非正整数的数值作为参数,会报错
    new Array(3.2) // RangeError: Invalid array length
    new Array(-3) // RangeError: Invalid array length
    // 单个非数值(比如字符串、布尔值、对象等)作为参数,
    // 则该参数是返回的新数组的成员
    new Array('abc') // ['abc']
    new Array([1]) // [Array[1]]
    // 多参数时,所有参数都是返回的新数组的成员
    new Array(1, 2) // [1, 2]
    new Array('a', 'b', 'c') // ['a', 'b', 'c']

      

      5.3 数组的length属性

      (1)arr.length 数组的length属性,返回数组的成员数量  

      • JavaScript 使用一个32位整数,保存数组的元素个数。这意味着,数组成员最多只有 4294967295 个(232 - 1)个,也就是说length属性的最大值就是 4294967295
      • 如果最后一个元素后面有逗号,并不会产生空位。也就是说,有没有这个逗号,结果都是一样的
      • delete命令删除一个数组成员,会形成空位,并且不会影响length属性

      (2)length属性是可写的

      • 如果人为设置一个小于当前成员个数的值,该数组的成员会自动减少到length设置的值  ;清空数组的一个有效方法,就是将length属性设为0
      • 如果人为设置length大于当前元素个数,则数组的成员数量会增加到这个值,新增的位置都是空位 ;length属性设为大于数组个数时,读取新增的位置都会返回undefined

      (3)可使用检查某个键名是否存在的运算符in。   

      5.4 数组静态方法Array.isArray()

       Array.isArray方法返回一个布尔值,表示参数是否为数组。它可以弥补typeof运算符的不足

      5.5数组常见方法

      (1)valueOf方法是一个所有对象都拥有的方法,表示对该对象求值;alueOf方法返回数组本身;

      (2)toString方法也是对象的通用方法,数组的toString方法返回数组的字符串形式。

      (3)push方法用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组。

      (4)pop方法用于删除数组的最后一个元素,并返回该元素。注意,该方法会改变原数组。对空数组使用pop方法,不会报错,而是返回undefined

      (5)shift方法用于删除数组的第一个元素,并返回该元素。shift方法可以遍历并清空一个数组。

      (6)unshift方法用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组。

      (7)join方法以指定参数作为分隔符,将所有数组成员连接为一个字符串返回。如果不提供参数,默认用逗号分隔。如果数组成员是undefinednull或空位,会被转成空字符串

    var a = [1, 2, 3, 4];
    a.join(' ') // '1 2 3 4'
    a.join(' | ') // "1 | 2 | 3 | 4"
    a.join() // "1,2,3,4"

      (8)concat方法用于多个数组的合并。它将新数组的成员,添加到原数组成员的后部,然后返回一个新数组,原数组不变。

    ['hello'].concat(['world'])
    // ["hello", "world"]
    ['hello'].concat(['world'], ['!'])
    // ["hello", "world", "!"]
    [].concat({a: 1}, {b: 2})
    // [{ a: 1 }, { b: 2 }]
    [2].concat({a: 1})
    // [2, {a: 1}]

      (9)reverse方法用于颠倒排列数组元素,返回改变后的数组。注意,该方法将改变原数组。

      (10)slice方法用于提取目标数组的一部分,返回一个新数组,原数组不变。arr.slice(start, end);

         slice方法的一个重要应用,是将类似数组的对象转为真正的数组。

    Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 })
    // ['a', 'b']
    Array.prototype.slice.call(document.querySelectorAll("div"));
    Array.prototype.slice.call(arguments);

      (11)splice方法用于删除原数组的一部分成员,并可以在删除的位置添加新的数组成员,返回值是被删除的元素。注意,该方法会改变原数组。

          arr.splice(start, count, addElement1, addElement2, ...);
    var a = ['a', 'b', 'c', 'd', 'e', 'f'];
    a.splice(4, 2) // ["e", "f"]
    a // ["a", "b", "c", "d"]
    var a = ['a', 'b', 'c', 'd', 'e', 'f'];
    a.splice(4, 2, 1, 2) // ["e", "f"]
    a // ["a", "b", "c", "d", 1, 2]
    var a = [1, 1, 1];
    a.splice(1, 0, 2) // []
    a // [1, 2, 1, 1]
    var a = [1, 2, 3, 4];
    a.splice(2) // [3, 4]
    a // [1, 2] 如果只提供第一个参数,等同于将原数组在指定位置拆分成两个数组

      (12)sort方法对数组成员进行排序,默认是按照字典顺序排序。排序后,原数组将被改变。如果想让sort方法按照自定义方式排序,可以传入一个函数作为参数

      [ { name: "张三", age: 30 },{ name: "李四", age: 24 },{ name: "王五", age: 28  }].sort(function (o1, o2) {return o1.age - o2.age;})

      (13)map方法将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回;

      var numbers = [1, 2, 3];
      numbers.map(function (n) {
          return n + 1;
      });// [2, 3, 4]
      numbers// [1, 2, 3]
    • map方法的回调函数有三个参数,elem为当前成员的值,index为当前成员的位置,arr为原数组([1, 2, 3])。
    • map方法还可以接受第二个参数,用来绑定回调函数内部的this变量

      (14)forEach 方法与map方法很相似,也是对数组的所有成员依次执行参数函数。但是,forEach方法不返回值,只用来操作数据。这就是说,如果数组遍历的目的是为了得到返回值,那么使用map方法,否则使用forEach方法。

    •  注意,forEach方法无法中断执行,总是会将所有成员遍历完。如果希望符合某种条件时,就中断遍历,要使用for循环 ;
    •  forEach方法不会跳过undefinednull,但会跳过空位。;
    var out = [];
    [1, 2, 3].forEach(function(elem) {
      this.push(elem * elem);
    }, out);
    out // [1, 4, 9]

      (15)filter方法用于过滤数组成员,满足条件的成员组成一个新数组返回。

         它的参数是一个函数,所有数组成员依次执行该函数,返回结果为true的成员组成一个新数组返回。该方法不会改变原数组

    [1, 2, 3, 4, 5].filter(function (elem) {
      return (elem > 3);
    })
    // [4, 5]

      (16)some方法是只要一个成员的返回值是true,则整个some方法的返回值就是true,否则返回false

    var arr = [1, 2, 3, 4, 5];
    arr.some(function (elem, index, arr) {
      return elem >= 3;
    });
    // true

      (17)every方法是所有成员的返回值都是true,整个every方法才返回true,否则返回false

    var arr = [1, 2, 3, 4, 5];
    arr.every(function (elem, index, arr) {
      return elem >= 3;
    });
    // false

      (18)reduce方法和reduceRight方法依次处理数组的每个成员,最终累计为一个值。它们的差别是,reduce是从左到右处理(从第一个成员到最后一个成员),reduceRight则是从右到左(从最后一个成员到第一个成员),其他完全一样。

    [1, 2, 3, 4, 5].reduce(function (a, b) {
      console.log(a, b);
      return a + b;
    })
    // 1 2
    // 3 3
    // 6 4
    // 10 5
    //最后结果:15

      (19)indexOf方法返回给定元素在数组中第一次出现的位置,如果没有出现则返回-1

          indexOf方法还可以接受第二个参数,表示搜索的开始位置。

    ['a', 'b', 'c'].indexOf('a', 1) // -1

      (20)lastIndexOf方法返回给定元素在数组中最后一次出现的位置,如果没有出现则返回-1

    6、函数(Function)

    •  函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。
    • JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。

      6.1函数的声明

      (1)function命令声明的代码区块,就是一个函数。function命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面。

    function print(s) {
      console.log(s);
    }

      (2)采用变量赋值的写法

    var print = function(s) {
      console.log(s);
    };

      (3)Function构造函数

        Function构造函数接受三个参数,除了最后一个参数是add函数的“函数体”,其他参数都是add函数的参数

    var add = new Function(
      'x',
      'y',
      'return x + y'
    );

       6.2函数的使用

      (1)调用函数时,要使用圆括号运算符。圆括号之中,可以加入函数的参数。

      (2)函数体内部的return语句,表示返回。JavaScript 引擎遇到return语句,就直接返回return后面的那个表达式的值,后面即使还有语句,也不会得到执行。也就是说,return语句所带的那个表达式,就是函数的返回值。return语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回undefined

      (3)函数可以调用自身,这就是递归(recursion)。下面就是通过递归,计算斐波那契数列的代码。

    function fib(num) {
      if (num === 0) return 0;
      if (num === 1) return 1;
      return fib(num - 2) + fib(num - 1);
    }
    fib(6) // 8

      6.3函数与数据类型

    • JavaScript 语言将函数看作一种值,与其它值(数值、字符串、布尔值等等)地位相同。凡是可以使用值的地方,就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。函数只是一个可以执行的值,此外并无特殊之处。
    • 由于函数与其他数据类型地位平等,所以在 JavaScript 语言中又称函数为第一等公民。
    function add(x, y) {
      return x + y;
    }
    // 将函数赋值给一个变量
    var operator = add;
    // 将函数作为参数和返回值
    function a(op){
      return op;
    }
    a(add)(1, 1) // 函数柯里化 
    // 2

      6.4函数名的提升

      JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。所以,下面的代码不会报错

    f();
    function f() {}

      表面上,上面代码好像在声明之前就调用了函数f。但是实际上,由于“变量提升”,函数f被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript 就会报错。

    f();
    var f = function (){};
    // TypeError: undefined is not a function

      6.5不得在非函数的代码块中声明函数,最常见的情况就是iftry语句

    if (foo) {
      function x() {}
    }
    try {
      function x() {}
    } catch(e) {
      console.log(e);
    }

      6.6函数的属性和方法

      (1)f.name 函数的name属性返回函数的名字

      (2)f.length属性返回函数预期传入的参数个数,即函数定义之中的参数个数

      (3)f.toString方法返回一个字符串,内容是函数的源码

      6.7函数的作用域

      (1)作用域(scope)指的是变量存在的范围。在 ES5 的规范中,Javascript 只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。

    •   函数外部声明的变量就是全局变量(global variable),它可以在函数内部读取。
    •  在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)
    • 函数内部定义的变量,会在该作用域内覆盖同名全局变量。

      (2)与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部

    function foo(x) {
      if (x > 100) {
        var tmp = x - 100;
      }
    }
    // 等同于
    function foo(x) {
      var tmp;
      if (x > 100) {
        tmp = x - 100;
      };
    }

      (3)函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。

    var a = 1;
    var x = function () {
      console.log(a);
    };
    function f() {
      var a = 2;
      x();
    }
    f() // 1

      函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2

      函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。

      同样的,函数体内部声明的函数,作用域绑定函数体内部

    function foo() {
      var x = 1;
      function bar() {
        console.log(x);
      }
      return bar;
    }
    
    var x = 2;
    var f = foo();
    f() // 1

      6.8参数

      函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。

      (1)函数参数不是必需的,Javascript 允许省略参数。

      (2)如果有同名的参数,则取最后出现的那个值

      (3)arguments 对象

    •  由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是arguments对象的由来

    • arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。
    var f = function (one) {
      console.log(arguments[0]);
      console.log(arguments[1]);
      console.log(arguments[2]);
    }
    f(1, 2, 3)
    // 1
    // 2
    // 3

      正常模式下,arguments对象可以在运行时修改。

      严格模式下,arguments对象是一个只读对象,修改它是无效的,但不会报错。

      通过arguments对象的length属性,可以判断函数调用时到底带几个参数。

      虽然arguments很像数组,但它是一个对象。数组专有的方法(比如sliceforEach),不能在arguments对象上直接使用

      如果要让arguments对象使用数组方法,真正的解决方法是将arguments转为真正的数组。下面是两种常用的转换方法:slice方法和逐一填入新数组。

    var args = Array.prototype.slice.call(arguments);
    
    // 或者
    var args = [];
    for (var i = 0; i < arguments.length; i++) {
      args.push(arguments[i]);
    }

      arguments对象带有一个callee属性,返回它所对应的原函数。

    var f = function () {
      console.log(arguments.callee === f);
    }
    
    f() // true

      6.9闭包

      变量作用域。前面提到,JavaScript 有两种作用域:全局作用域和函数作用域。函数内部可以直接读取全局变量。

    var n = 999;
    function f1() {
      console.log(n);
    }
    f1() // 999

      上面代码中,函数f1可以读取全局变量n。但是,函数外部无法读取函数内部声明的变量。

    function f1() {
      var n = 999;
    }
    
    console.log(n)

      上面代码中,函数f1内部声明的变量n,函数外是无法读取的

      如果出于种种原因,需要得到函数内的局部变量。正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。

    function f1() {
      var n = 999;
      function f2() {
      console.log(n); // 999
      }
    }

    上面代码中,函数f2就在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是 JavaScript 语言特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

    既然f2可以读取f1的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗  

    function f1() {
      var n = 999;
      function f2() {
        console.log(n);
      }
      return f2;
    }
    var result = f1();
    result(); // 999

    上面代码中,函数f1的返回值就是函数f2,由于f2可以读取f1的内部变量,所以就可以在外部获得f1的内部变量了

    闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。

    function createIncrementor(start) {
      return function () {
        return start++;
      };
    }
    
    var inc = createIncrementor(5);
    
    inc() // 5
    inc() // 6
    inc() // 7

    上面代码中,start是函数createIncrementor的内部变量。通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc使得函数createIncrementor的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。为什么会这样呢?原因就在于inc始终在内存中,而inc的存在依赖于createIncrementor,因此也始终在内存中,不会在调用结束后,被垃圾回收机制回收。

    闭包的另一个用处,是封装对象的私有属性和私有方法

    function Person(name) {
      var _age;
      function setAge(n) {
        _age = n;
      }
      function getAge() {
        return _age;
      }
    
      return {
        name: name,
        getAge: getAge,
        setAge: setAge
      };
    }
    
    var p1 = Person('张三');
    p1.setAge(25);
    p1.getAge() // 25

    上面代码中,函数Person的内部变量_age,通过闭包getAgesetAge,变成了返回对象p1的私有变量。

    注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。

      6.10立即调用的函数表达式

      在 Javascript 中,圆括号()是一种运算符,跟在函数名之后,表示调用该函数。比如,print()就表示调用print函数。

    (function(){ /* code */ }());
    // 或者
    (function(){ /* code */ })();

      上面两种写法都是以圆括号开头,引擎就会认为后面跟的是一个表示式,而不是函数定义语句,所以就避免了错误。这就叫做“立即调用的函数表达式”(Immediately-Invoked Function Expression),简称 IIFE。

      注意,上面两种写法最后的分号都是必须的。如果省略分号,遇到连着两个 IIFE,可能就会报错。

    !function () { /* code */ }();
    ~function () { /* code */ }();
    -function () { /* code */ }();
    +function () { /* code */ }();

       通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。

    // 写法一
    var tmp = newData;
    processData(tmp);
    storeData(tmp);
    
    // 写法二
    (function () {
      var tmp = newData;
      processData(tmp);
      storeData(tmp);
    }());

     

    7、null 和 undefined

     7.1 undefined是一个表示”此处无定义”的原始值,转为数值时为NaN

     7.2  null是一个表示“空”的对象,转为数值时为0

    if (!undefined) {
      console.log('undefined is false');
    }
    // undefined is false
    
    if (!null) {
      console.log('null is false');
    }
    // null is false
    
    undefined == null
    // true
    
    Number(null) // 0
    5 + null // 5
    
    Number(undefined) // NaN
    5 + undefined // NaN

     

    三、数据类型判断

    1、typeof

    typeof运算符可以返回一个值的数据类型

    • 数值、字符串、布尔值分别返回numberstringboolean
    • 函数返回function
    • undefined返回undefined
    • 对象返回object
    typeof 123 // "number"
    typeof '123' // "string"
    typeof false // "boolean"
    
    function f() {}
    typeof f  // "function"
    
    typeof undefined // "undefined"
    
    typeof window // "object"
    typeof {} // "object"
    typeof [] // "object"

    2、instanceof

    instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例

    (1)instanceof运算符的左边是实例对象,右边是构造函数。它会检查右边构建函数的原型对象(prototype),是否在左边对象的原型链上。

    var v = new Vehicle();
    v instanceof Vehicle // true

    (2)由于instanceof检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回true

    var d = new Date();
    d instanceof Date // true
    d instanceof Object // true

    (3)instanceof的原理是检查右边构造函数的prototype属性,是否在左边对象的原型链上。有一种特殊情况,就是左边对象的原型链上,只有null对象。这时,instanceof判断会失真。

    var obj = Object.create(null);
    typeof obj // "object"
    Object.create(null) instanceof Object // false

    (4)区别array和obj

    var x = [1, 2, 3];
    var y = {};
    x instanceof Array // true
    y instanceof Object // true

     (5)instanceof运算符只能用于对象,不适用原始类型的值。

    var s = 'hello';
    s instanceof String // false

     (6)对于undefinednullinstanceOf运算符总是返回false

    undefined instanceof Object // false
    null instanceof Object // false

     3、Object.prototype.toString

    (1)基本类型判断

    console.log(Object.prototype.toString.call("jerry"));//[object String]
    console.log(Object.prototype.toString.call(12));//[object Number]
    console.log(Object.prototype.toString.call(true));//[object Boolean]
    console.log(Object.prototype.toString.call(undefined));//[object Undefined]
    console.log(Object.prototype.toString.call(null));//[object Null]

    (2)判断原生引用类型

    console.log(Object.prototype.toString.call({name: "jerry"}));//[object Object]
    console.log(Object.prototype.toString.call(function(){}));//[object Function]
    console.log(Object.prototype.toString.call([]));//[object Array]
    console.log(Object.prototype.toString.call(new Date));//[object Date]
    console.log(Object.prototype.toString.call(/d/));//[object RegExp]

    (3)自定义类型

    这种方法不能准确判断personPerson类的实例,而只能用instanceof 操作符来进行判断

    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    var person = new Person("Rose", 18);
    Object.prototype.toString.call(arr); // "[object Object]"

    注:

    部分内容引用和参考:JavaScript 标准参考教程

  • 相关阅读:
    Java 常见异常种类
    Oracle存储过程记录异常
    oracle定时器
    oracle生成单据号
    oracle计算时间秒数差
    oracle分组后取每组第一条数据
    was部分更新
    数据库分区
    JTA事务管理
    docker
  • 原文地址:https://www.cnblogs.com/pangys/p/5592297.html
Copyright © 2011-2022 走看看