基本数据类型
JavaScript基本数据类型有Number,String,Boolean,Null,Undefined,symbol,bigint。其中symbol,bigint是ES6新增的。
Number-数字
JavaScript 的Number
类型为双精度 IEEE 754 64 位浮点类型。JavaScript 不区分整数值和浮点数值,所有数字在 JavaScript 中均用浮点数值表示,所以在进行数字运算的时候要特别注意进度缺失问题。
为什么0.1 + 0.2 != 0.3 ?
因为浮点数运算的精度问题。在计算机运行过程中,需要将数据转化成二进制,然后再进行计算。js中的Number类型遵循IEEE754标准,在IEEE754标准的64位浮点数相加,因为浮点数自身小数位数的限制而截断的二进制在转化为十进制,就变成0.30000000000000004,所以在计算时会产生误差。
解决方案:
第一种:NumberObject.toFixed(num)
NumberObject.toFixed(num) 方法可把 Number 四舍五入为指定小数位数的数字。num规定小数的位数,取值范围 [0, 20] ,默认值为0。var num = new Number(13.37); console.log(num.toFixed(1));//13.4
第二种:Number.EPSILON
Number.EPSILON属性表示 1 与Number可表示的大于 1 的最小的浮点数之间的差值。可以用来设置“能够接受的误差范围”。比如,误差范围设为 2 的-50 次方,如果两个浮点数的差小于这个值,我们就认为这两个浮点数相等。var x = 0.2; var y = 0.3; var z = 0.1; var equal = (x + z - y) == 0;//false var var equal = ((x + z - y) < Number.EPSILON);//true
String-字符串
字符串可以存储一系列字符,如 "zhang"。字符串可以是插入到引号中的任何字符。可以使用单引号或双引号。
var s = 'aidsfjiajdpof';
var b = s.length;//13
var c = s[3];//s
Boolean-布尔类型
布尔(逻辑)只能有两个值:true 或 false。
Null和Undefined
Null 和 Undefined 都代表空,主要区别在于 undefined
表示尚未初始化的变量的值,而 null
表示该变量有意缺少对象指向。
引用/复杂数据类型
JavaScript引用/复杂数据类型有Array、Object、Function。
Array-数组
- 创建数据的方法
1. 创建数组并给数组赋值
var myCars = new Array(); myCars[0] = "Saab"; myCars[1] = "Volvo"; myCars[2] = "BMW";
2. 直接实例化
var myCars = new Array("Saab","Volvo","BMW");
3. 字面-隐式创建
var myCars = ["Saab","Volvo","BMW"];
- 数组的常用操作
1. concat()
用于连接两个或多个数组,返回新数组不会改变现有的数组。
var arr1 = [1, 2, 3] var arr2 = arr1.concat(arr1, 22); console.log(arr1 === arr2); //false console.log(arr1.concat("hello", "world")); //[1,2,3,"hello","world"] console.log(arr1.concat(["a", "b"], [[3, 4], {"name": "admin"}])); //[1,2,3,"a","b",[3,4],{"name":"admin"}] console.log(arr1); //[1,2,3]---原数组未改变
2. join()
根据指定分隔符将数组中的所有元素连接放入一个字符串,并返回这个字符串。join(str);参数默认为","号。不改变原数组。
var arr = [1,2,3]; console.log(arr.join()); //1,2,3 console.log(arr.join("-")); //1-2-3 console.log(arr); //[1,2,3]---原数组未改变
3. pop()
用于删除并返回数组的最后一个元素。原数组改变。
4. shift()
用于删除并返回数组的第一个元素。原数组改变。
5. unshift()
向数组的开头添加一个或多个元素,并返回新的长度。原数组改变。
6. push()
向数组的末尾添加一个或更多元素,并返回新的长度。原数组改变。
7. reverse()
翻转数组中元素的顺序。原数组改变。
8. slice()
从已有的数组中返回选定的元素。该方法接收两个参数slice(start,end),strat为必选,表示从第几位开始;end为可选,表示到第几位结束(不包含end位),省略表示到最后一位;start和end都可以为负数,负数时表示从最后一位开始算起,如-1表示最后一位。不改变原数组。
var arr = ["A","B","C","D","E"]; console.log(arr.slice(1,3)); //["B","C"] console.log(arr.slice(1)); //["B","C","D","E"] console.log(arr.slice(-4,-1)); //["B","C","D"] console.log(arr.slice(-2)); //["D","E"] console.log(arr.slice(1,-2)); //["B","C"] console.log(arr); //["A","B","C","D","E"]---原数组未改变
9. sort()
对数组中的元素进行排序,默认是升序。原数组改变。
注意:但是在排序前,会先调用数组的toString方法,将每个元素都转成字符之后,再进行排序,此时会按照字符串的排序,逐位比较,进行排序。
var arr = [6,1,5,2,3]; console.log(arr.sort()); //[1, 2, 3, 5, 6] console.log(arr); //[1, 2, 3, 5, 6]---原数组改变 var arr1 = [6,1024,52,256,369]; console.log(arr1.sort()); //[1024, 256, 369, 52, 6] console.log(arr1); //[1024, 256, 369, 52, 6]---原数组改变
解决方法:sort(compareFunction),如果需要按照数值排序,需要传参。compareFunction为回调函数,该函数应该具有两个参数,比较这两个参数,然后返回一个用于说明这两个值的相对顺序的数字。
- 如果
compareFunction(a, b)
小于 0 ,那么 a 会被排列到 b 之前;
- 如果
compareFunction(a, b)
等于 0 , a 和 b 的相对位置不变。
- 如果
compareFunction(a, b)
大于 0 , b 会被排列到 a 之前。compareFunction(a, b)
必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的var arr = [6,1024,52,256,369]; console.log(arr.sort(fn)); //[6, 52, 256, 369, 1024] console.log(arr); //[6, 52, 256, 369, 1024]---原数组改变 function fn(a,b){ return a-b; }
10. splice()
向数组中添加,或从数组删除,或替换数组中的元素,然后返回被删除/替换的元素。原数组为修改后的数组。array.splice(start,deleteCount,
item1, item2, ...
)
start
:指定修改的开始位置(从 0 计数)。
deleteCount
:表示要移除的数组元素的个数。
item1, item2, ...
:要添加进数组的元素,从start
位置开始。如果不指定,则splice()
将只删除数组元素。返回值:由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组。
11. toString()
转换成字符串,类似于没有参数的join()。该方法会在数据发生隐式类型转换时被自动调用,如果手动调用,就是直接转为字符串。不改变原数组。
var arr = [1,2,3]; console.log(arr.toString()); //1,2,3 console.log(arr); //[1,2,3]---原数组未改变
12. valueOf()
返回数组的原始值(一般情况下其实就是数组自身),一般由js在后台调用,并不显式的出现在代码中
var arr = [1,2,3]; console.log(arr.valueOf()); //[1,2,3] console.log(arr); //[1,2,3] //为了证明返回的是数组自身 console.log(arr.valueOf() == arr); //true
13. indexOf()
根据指定的数据,从左向右,查询在数组中出现的位置,如果不存在指定的数据,返回-1。该方法是查询方法,不会对数组产生改变。indexOf(value, start);value为要查询的数据;start为可选,表示开始查询的位置,当start为负数时,从数组的尾部向前数;如果查询不到value的存在,则方法返回-1。
14. forEach()
ES5新增方法,用来遍历数组,该方法没有返回值。forEach接收的回调函数会根据数组的每一项执行,该回调函数默认有三个参数,分别为:遍历到的数组的数据,对应的索引,数组自身。
const arraySparse = [1,3,7]; let numCallbackRuns = 0; arraySparse.forEach(function(element){ console.log(element); numCallbackRuns++; }); //1 //3 //7
15. map()
同forEach功能;map的回调函数会将执行结果返回,最后map将所有回调函数的返回值组成新数组返回。
const array1 = [1, 4, 9, 16]; // pass a function to map const map1 = array1.map(x => x * 2); console.log(map1); // expected output: Array [2, 8, 18, 32]
16. filter()
同forEach功能;filter的回调函数需要返回布尔值,当为true时,将本次数组的数据返回给filter,最后filter将所有回调函数的返回值组成新数组返回(此功能可理解为“过滤”)。
const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present']; const result = words.filter(word => word.length > 6); console.log(result); // expected output: Array ["exuberant", "destruction", "present"]
17. every()
判断数组中每一项是否都满足条件,只有所有项都满足条件,才会返回true。
every
方法为数组中的每个元素执行一次callback
函数,直到它找到一个会使callback
返回 false 的元素。如果发现了一个这样的元素,every
方法将会立即返回false
。否则,callback
为每一个元素返回true
,every
就会返回true
。every
不会改变原数组。18.
some()
测试数组中是不是至少有 1 个元素通过了被提供的函数测试。它返回的是一个 Boolean 类型的值。找到符合条件的就立即返回true。
Object-对象
- 创建对象的方法
1. new Object();
var myCar = new Object(); myCar.make = "Ford"; myCar.model = "Mustang"; myCar.year = 1969;
2. 使用对象初始化器(通过字面值)创建对象
var obj = { name: 'zhang', age: 18, 'address': '吉林', };
3. 使用构造函数
function Car(make, model, year) { this.make = make; this.model = model; this.year = year; } var mycar = new Car("Eagle", "Talon TSi", 1993);
Function-函数
每个 JavaScript 函数实际上都是一个 Function
对象。运行 (function(){}).constructor === Function // true
便可以得到这个结论。
Function 构造函数与函数声明之间的不同
由 Function
构造函数创建的函数不会创建当前环境的闭包,它们总是被创建于全局环境,因此在运行时它们只能访问全局变量和自己的局部变量,不能访问它们被 Function
构造函数创建时所在的作用域的变量。这一点与使用 eval() 执行创建函数的代码不同。
var x = 10;
function createFunction1() {
var x = 20;
return new Function('return x;'); // 这里的 x 指向最上面全局作用域内的 x
}
function createFunction2() {
var x = 20;
function f() {
return x; // 这里的 x 指向上方本地作用域内的 x
}
return f;
}
var f1 = createFunction1();
console.log(f1()); // 10
var f2 = createFunction2();
console.log(f2()); // 20
Function方法
1. Function.prototype.apply()
apply()
方法调用一个具有给定this
值的函数,以及以一个数组(或一个类数组对象)的形式提供的参数。返回值:调用有指定this
值和参数的函数的结果。
thisArg
在
func
函数运行时使用的this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为null
或undefined
时会自动替换为指向全局对象,原始值会被包装。
argsArray
可选一个数组或者类数组对象,其中的数组元素将作为单独的参数传给
func
函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。当第一个参数为 null 或 undefined 时,可以使用数组展开语法实现类似的结果
//用 apply 将数组各项添加到另一个数组 const array = ['a', 'b']; const elements = [0, 1, 2]; array.push.apply(array, elements); console.info(array); // ["a", "b", 0, 1, 2] //使用 apply 和内置函数 const numbers = [5, 6, 2, 3, 7]; // 使用 Math.max 以及 apply 函数时的代码 let max = Math.max.apply(null, numbers); // 基本等同于 Math.max(numbers[0], ...) 或Math.max(5, 6, ..) //apply第一个参数为nul或undefined时和数组展开语法 function sum(x, y, z) { return x + y + z; } const numbers = [1, 2, 3]; console.log(sum(...numbers)); // expected output: 6 console.log(sum.apply(null, numbers)); // expected output: 6
bind()
方法创建一个新的函数,在bind()
被调用时,这个新函数的this
被指定为bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。返回一个原函数的拷贝,并拥有指定的this
值和初始参数。
.bind(thisArg[, arg1[, arg2[, ...]]])
- 用法一:创建绑定函数
bind()
最简单的用法是创建一个函数,不论怎么调用,这个函数都有同样的this
值。JavaScript 新手经常犯的一个错误是将一个方法从对象中拿出来,然后再调用,期望方法中的this
是原来的对象(比如在回调中传入这个方法)。如果不做特殊处理的话,一般会丢失原来的对象。基于这个函数,用原始的对象创建一个绑定函数,巧妙地解决了这个问题:this.x = 9; // 在浏览器中,this 指向全局的 "window" 对象 var module = { x: 81, getX: function() { return this.x; } }; module.getX(); // 81 var retrieveX = module.getX; retrieveX(); // 返回 9 - 因为函数是在全局作用域中调用的 // 创建一个新函数,把 'this' 绑定到 module 对象 var boundGetX = retrieveX.bind(module); boundGetX(); // 81
- 用法二:配合setTimeout
在默认情况下,使用 window.setTimeout() 时,
this
关键字会指向 window(或global
)对象。当类的方法中需要this
指向类的实例时,你可能需要显式地把this
绑定到回调函数,就不会丢失该实例的引用。function LateBloomer() { this.petalCount = Math.ceil(Math.random() * 12) + 1; } // 在 1 秒钟后声明 bloom LateBloomer.prototype.bloom = function() { window.setTimeout(this.declare.bind(this), 1000); }; LateBloomer.prototype.declare = function() { console.log('I am a beautiful flower with ' + this.petalCount + ' petals!'); }; var flower = new LateBloomer(); flower.bloom(); // 一秒钟后,调用 'declare' 方法
call()
方法使用一个指定的this
值和单独给出的一个或多个参数来调用一个函数。该方法的语法和作用与 apply() 方法类似,只有一个区别,就是call()
方法接受的是一个参数列表,而apply()
方法接受的是一个包含多个参数的数组。如果没有传递第一个参数,this
的值将会被绑定为全局对象。在严格模式('use strict')下,this
的值将会是undefined
。
- 用法一:调用父构造函数
function Product(name, price) { this.name = name; this.price = price; } function Food(name, price) { Product.call(this, name, price); this.category = 'food'; } function Toy(name, price) { Product.call(this, name, price); this.category = 'toy'; } var cheese = new Food('feta', 5); var fun = new Toy('robot', 40);
- 用法二:调用函数指定上下文的'this'
function greet() { var reply = [this.animal, 'typically sleep between', this.sleepDuration].join(' '); console.log(reply); } var obj = { animal: 'cats', sleepDuration: '12 and 16 hours' }; greet.call(obj); // cats typically sleep between 12 and 16 hours
两种数据类型的存储方式
-
堆:引用数据类型 -> 对象(Object) 数组(Array) 函数(Function)
-
栈:原始数据类型
两种数据类型的区别
两种类型的区别主要在于存储位置不同:
原始数据类型直接存放在栈(stack)中的简单数据段,占据空间小,大小固定,属于频繁被使用的数据所以放在栈中存储。引用数据类型存储在堆中的对象,占据空间大,大小不固定。如果将引用数据类型存储在栈中会影响程序运行的性能,引用数据类型是在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
在数据结构中,栈中数据的存取方式为先进后出,堆是一个优先级队列,按照优先级进行排序。
在操作系统中,内存被分为堆区和栈区,栈区内存由编译器自动分配,存放函数的参数值,局部变量的值等。堆区内存一般由开发者分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。
如何判断数据类型
1. typeof
数组、对象、null都会被判断为object
console.log(typeof 2);//number
console.log(typeof true);//boolean
console.log(typeof '2');//string
console.log(typeof []);//object
console.log(typeof {});//object
console.log(typeof function() {});//function
console.log(typeof null);//object
console.log(typeof undefined);//undefined
2. instanceof
instanceof可以正确判断对象的类型,其内部运行机制是通过判断在其原型链中是否能找到该类型的原型。
console.log(2 instanceof Number);//false
console.log(true instanceof Boolean);//false
console.log('2' instanceof String);//false
console.log([] instanceof Array);//true
console.log({} instanceof Object);//true
console.log(function() {} instanceof Function);//true
3. constructor
constructor可以判断数据类型,对象实例也可以通过constructor访问它的构造函数。但是如果创建一个对象来改变他的原型,constructor就不能用来判断数据类型了。
console.log((2).constructor === Number); //true
console.log((true).constructor === Boolean);//true
console.log(('2').constructor === String);//true
console.log(([]).constructor === Array);//true
console.log(({}).constructor === Object);//true
console.log((function() {}).constructor === Function);//true
function Fn() {};
Fn.prototype = new Array();
var f = new Fn();
console.log(f.constructor === Fn);//false
console.log(f.constructor === Array);//true
4. Object.prototype.toString.call()
var a = Object.prototype.toString;
console.log(a.call(2));//[object Number]
console.log(a.call(true));//[object Boolean]
console.log(a.call('2'));//[object String]
console.log(a.call([]));//[object Array]
console.log(a.call({}));//[object Object]
console.log(a.call(function() {}));//[object Function]
数据类型转换
1. 把数值、布尔类型转换为字符串
String(x) // 从数值变量 x 返回字符串
String(123) // 从数值文本 123 返回字符串
String(100 + 23) // 从表达式中的数值返回字符串
x.toString()
(123).toString()
(100 + 23).toString()
String(false) // 返回 "false"
String(false) // 返回 "false"
2. 把字符串,布尔值转换为数值
Number("3.14") // 返回 3.14
Number(" ") // 返回 0
Number("") // 返回 0
Number("99 88") // 返回 NaN
var y = "5"; // y 是字符串
var x = + y; // x 是数字
var y = "Bill"; // y 是字符串
var x = + y; // x 是数字 (NaN)
Number(false) // 返回 0
Number(true) // 返回 1
3. 自动类型转换
5 + null // 返回 5 因为 null 被转换为 0
"5" + null // 返回 "5null" 因为 null 被转换为 "null"
"5" + 2 // 返回 52 因为 2 被转换为 "2"
"5" - 2 // 返回 3 因为 "5" 被转换为 5
"5" * "2" // 返回 10 因为 "5" 和 "2" 被转换为 5 和 2
ES6新增数据类型
Symbol
Symbol()
函数会返回 symbol 类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的 symbol 注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()
"。每个从 Symbol()
返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;这是该数据类型仅有的目的。
//Symbol("foo") 不会强制将字符串 “foo” 转换成 symbol 类型。它每次都会创建一个新的 symbol 类型
Symbol("foo") === Symbol("foo"); // false
BigInt
BigInt
是一种内置对象,它提供了一种方法来表示大于 2^53 - 1
( 这原本是 Javascript 中可以用 Number 表示的最大数字)的整数。BigInt
可以表示任意大的整数。 它在某些方面类似于 Number ,但是也有几个关键的不同点:不能用于 Math 对象中的方法;不能和任何 Number 实例混合运算,两者必须转换成同一种类型。在两种类型来回转换时要小心,因为 BigInt
变量在转换成 Number 变量时可能会丢失精度。