[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 标准参考教程

原文地址:https://www.cnblogs.com/pangys/p/5592297.html