一、JavaScript基础
1、变量和类型
(1)JavaScript规定了几种语言类型
JavaScript中的每一个值都有它自己的类型,JavaScript规定了七种语言类型:
①Undefined
②Nulls
③Boolean
④String
⑤Number
⑥Symbol
⑦.Object
(2)JavaScript对象的底层数据结构是什么
js基本类型数据都是直接按值存储在栈中的(Undefined、Null、不是new出来的布尔、数字和字符串),每种类型的数据占用的内存空间的大小是确定的,并由系统自动分配和自动释放。这样带来的好处就是,内存可以及时得到回收,相对于堆来说 ,更加容易管理内存空间。
js引用类型数据被存储于堆中 (如对象、数组、函数等,它们是通过拷贝和new出来的)。其实,说存储于堆中,也不太准确,因为,引用类型的数据的地址指针是存储于栈中的,当我们想要访问引用类型的值的时候,需要先从栈中获得对象的地址指针,然后,在通过地址指针找到堆中的所需要的数据。
数据在内存中的存储结构,也就是物理结构,分为两种:顺序存储结构和链式存储结构。
顺序存储结构:是把数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。数组就是顺序存储结构的典型代表。
链式存储结构:是把数据元素存放在内存中的任意存储单元里,也就是可以把数据存放在内存的各个位置。这些数据在内存中的地址可以是连续的,也可以是不连续的。链表就是顺序存储结构的典型代表。
和顺序存储结构不同的是,链式存储结构的数据元素之间是通过指针来连接的,我们可以通使用指针来找到某个数据元素的位置,然后对这个数据元素进行一些操作。
数组和队列都可以实现栈和链表。
打个比方说一下顺序存储结构和链式存储结构的区别:
比如去银行取钱,顺序存储结构就相当于,所有的客户按照先来后到的顺序有序的的坐在大厅的椅子上(注意:是有顺序的坐着哦)。
而链式存储结构相当于,所有的客户只要一到银行,大堂经理就给他们每人一个号码,然后他们可以随便坐在哪个椅子上(随便坐,不需要按照什么顺序坐),只需要等待工作人员广播叫号即可。
而每个客户手里的号码就相当于指针,当前的指针指向下一个存储空间,这样,所有不连续的空间就可以被有顺序的按照线性连接在一起了。
(3)Symbol类型在实际开发中的应用、可手动实现一个简单的Symbol
// 没有参数的情况
var s1 = Symbol();
var s2 = Symbol();
s1 === s2 // false
// 有参数的情况
var s1 = Symbol("foo");
var s2 = Symbol("foo");
s1 === s2 // false
使用同一个 Symbol 值,可以使用 Symbol.for
var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');
console.log(s1 === s2); // true
(4)JavaScript中的变量在内存中的具体存储形式
栈内存和堆内存
JavaScript中的变量分为基本类型和引用类型
基本类型是保存在栈内存中的简单数据段,它们的值都有固定的大小,保存在栈空间,通过按值访问
引用类型是保存在堆内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,JavaScript不允许直接访问堆内存中的位置,因此操作对象时,实际操作对象的引用
归类如下
基本类型:
Number,String,Boolean,Null,Undefined,Symbol、
访问:基本数据类型的值是按值访问的。
存储:基本类型的变量是存放在栈内存(Stack)里的。
引用类型: Object
访问:引用类型的值是按引用访问的。
存储:引用类型的值是保存在堆内存(Heap)中的对象(Object)
typeof: 经常用来检测一个变量是不是最基本的数据类型
instanceof: 用来判断某个构造函数的 prototype 属性所指向的对象是否存在于另外一个要检测对象的原型链上。
简单说就是判断一个引用类型的变量具体是不是某种类型的对象
(5)基本类型对应的内置对象,以及他们之间的装箱拆箱操作
①内置对象:
Object是 JavaScript 中所有对象的父对象 数据封装类对象:Object、Array、Boolean、Number 和 String 其他对象:Function、Math、Date、RegExp、Error。
特殊的基本包装类型(String、Number、Boolean)
arguments: 只存在于函数内部的一个类数组对象
②装箱和拆箱
引用类型有个特殊的基本包装类型,它包括String、Number和Boolean。
作为字符串的a可以调用方法
装箱:
把基本数据类型转化为对应的引用数据类型的操作**,装箱分为隐式装箱和显示装箱。在《javascript高级程序设计》中有这样一句话:
每当读取一个基本类型的时候,后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据。(隐式装箱)
//隐式装箱
let a = 'sun'
let b = a.indexof('s') // 0 // 返回下标
// 上面代码在后台实际的步骤为:
let a = new String('sun')
let b = a.indexof('s')
a = null
//在上面的代码中,a是基本类型,它不是对象,不应该具有方法,js内部进行了一些列处理(装箱), 使得它能够调用方法。在这个基本类型上调用方法,其实是在这个基本类型对象上调用方法。这个基本类型的对象是临时的,它只存在于方法调用那一行代码执行的瞬间,执行方法后立刻被销毁。实现机制:
创建String类型的一个实例;
在实例上调用指定的方法;
销毁这个实例;
③装箱
显示装箱
通过内置对象可以对Boolean、Object、String等可以对基本类型显示装箱
let a = new String('sun ')
拆箱:
拆箱和装箱相反,就是把引用类型转化为基本类型的数据,通常通过引用类型的valueof()和toString()方法实现
let name = new String('sun')
let age = new Number(24)
console.log(typeof name) // object
console.log(typeof age) // object
// 拆箱操作
console.log(typeof age.valueOf()); // number // 24 基本的数字类型
console.log(typeof name.valueOf()); // string // 'sun' 基本的字符类型
console.log(typeof age.toString()); // string // '24' 基本的字符类型
console.log(typeof name.toString()); // string // 'sun' 基本的字符类型
(6)理解值类型和引用类型
1、值类型(ValueType)
值类型包括:数据类型、结构体、bool型、用户定义的结构体、枚举、可空类型
值类型的变量直接存储数据,分配在托管栈中。变量会在创建它们的方法返回时自动释放,例如在一个方法中声明Char型变量值时,name变量在栈上占有的内存就会自动释放
2、引用类型
引用类型的变量持有的式数据的引用,数据存储在数据堆,分配在托管堆中
值类型和引用类型的区别:
值类型 —— 引用类型
(1)存储方式: 直接存储数据本身 ——存储的是数据的引用,数据存储在数据堆中
(2)内存分配: 分配在栈中 ——分配在堆中
(3)效率: 效率高,不需要地址转换 ——效率较低,需要进行地址转换
(4)内存回收: 使用完后立即回收 ——使用完后不立即回收,而是交给GC处理回收
(5)赋值操作: 创建一个新对象 —— 创建一个引用
(6)类型扩展:不易扩展,所有类型都是密封的,所以无法 派生出新的类型。 —— 具有多态的特性方便扩展
(7)实例分配:通常是在线程栈上分配的(静态分配), 但是在某些情形下可以存储在堆中。 —— 总是在进程堆中分配(动态分配)
(7)null和undefined的区别
1.类型不一样:
console.log(typeOf undefined);//undefined
console.log(typeOf null);//object
2.转化bai为值时不一样:undefined为NaN ,null为0
console.log(Number(undefined));//NaN
console.log(Number(10+undefined));//NaN
console.log(Number(null));//0
console.log(Number(10+null));//10
3.undefined===null;//false
undefined==null;//true
(8)至少可以说出三种判断JavaScript数据类型的方式,以及他们的优缺点,如何准确的判断数组类型
1 typeof
typeof返回一个表示数据类型的字符串,返回结果包括: number boolean string object undefined function等
typeof可以对js基础数据类型做出准确的判断,在对于引用类型返回的基本上都是object。判断是引用类型,typeof使用起来比较不方便。
2 instanceof
instanceof是用来判断A是否为B的实例时,表达式为:A instanceof B,如果 A是B的实例,则返回true; 否则返回false 在这里特别注意的是 instanceof检测的是原型
3 Object.prototype.toString
toString是Object原型对象上的一个方法,该方法默认返回其调用者的具体类型,更严格的讲,是 toString运行时this指向的对象类型, 返回的类型格式为[object,xxx],xxx是具体的数据类型,其中包括:String,Number,Boolean,Undefined,Null,Function,Date,Array,RegExp,Error,HTMLDocument,… 基本上所有对象的类型都可以通过这个方法获取到。
4 constructor 查看对象对应的构造函数
construvtor在对应对象的原型下面,是自动生成的,当我们写一个构造函数的时候,程序自动添加,构造函数名.prototype.constructor = 构造函数名。
(9)可能发生隐式类型转换的场景以及转换原则,应如何避免或巧妙应用
JavaScript 默认自动转换,没有任何警告
隐式类型转换常见场景
例如自动转换 Boolean
例如 if 语句 或者其他需要 Boolean 的地方
if (表达式){}
运算符
在非 Numeber 类型进行数学运算符 - * / 时,会先将非 Number 转换成 Number 类型。
+ 运算符要考虑字符串的情况,在操作数中存在字符串时,优先转换成字符串,
+ 运算符其中一个操作数是字符串的话,会进行连接字符串的操作。
1+'2' // '12'
(10)出现小数精度丢失的原因,JavaScript可以存储的最大数字、最大安全数字,JavaScript处理大数字的方法、避免精度丢失的方法
①两个简单的浮点数相加
②大整数运算
9999999999999999 == 10000000000000001 // true
③JS 数字丢失精度的原因
计算机的二进制实现和位数限制有些数无法有限表示。就像一些无理数不能有限表示,如 圆周率 3.1415926…,1.3333… 等。JS 遵循 IEEE 754 规范,采用双精度存储(double precision),占用 64 bit。
④解决方案
对于整数,前端出现问题的几率可能比较低,毕竟很少有业务需要需要用到超大整数,只要运算结果不超过 Math.pow(2, 53) 就不会丢失精度。
对于小数,前端出现问题的几率还是很多的,尤其在一些电商网站涉及到金额等数据。解决方式:把小数放到位整数(乘倍数),再缩小回原来倍数(除倍数)
(function () {
var calc = {
/*
函数,加法函数,用来得到精确的加法结果
说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。
参数:arg1:第一个加数;arg2第二个加数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数)
调用:Calc.Add(arg1,arg2,d)
返回值:两数相加的结果
*/
Add: function (arg1, arg2) {
arg1 = arg1.toString(), arg2 = arg2.toString();
var arg1Arr = arg1.split("."), arg2Arr = arg2.split("."), d1 = arg1Arr.length == 2 ? arg1Arr[1] : "", d2 = arg2Arr.length == 2 ? arg2Arr[1] : "";
var maxLen = Math.max(d1.length, d2.length);
var m = Math.pow(10, maxLen);
var result = Number(((arg1 * m + arg2 * m) / m).toFixed(maxLen));
var d = arguments[2];
return typeof d === "number" ? Number((result).toFixed(d)) : result;
},
/*
函数:减法函数,用来得到精确的减法结果
说明:函数返回较为精确的减法结果。
参数:arg1:第一个加数;arg2第二个加数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数
调用:Calc.Sub(arg1,arg2)
返回值:两数相减的结果
*/
Sub: function (arg1, arg2) {
return Calc.Add(arg1, -Number(arg2), arguments[2]);
},
/*
函数:乘法函数,用来得到精确的乘法结果
说明:函数返回较为精确的乘法结果。
参数:arg1:第一个乘数;arg2第二个乘数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数)
调用:Calc.Mul(arg1,arg2)
返回值:两数相乘的结果
*/
Mul: function (arg1, arg2) {
var r1 = arg1.toString(), r2 = arg2.toString(), m, resultVal, d = arguments[2];
m = (r1.split(".")[1] ? r1.split(".")[1].length : 0) + (r2.split(".")[1] ? r2.split(".")[1].length : 0);
resultVal = Number(r1.replace(".", "")) * Number(r2.replace(".", "")) / Math.pow(10, m);
return typeof d !== "number" ? Number(resultVal) : Number(resultVal.toFixed(parseInt(d)));
},
/*
函数:除法函数,用来得到精确的除法结果
说明:函数返回较为精确的除法结果。
参数:arg1:除数;arg2被除数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数)
调用:Calc.Div(arg1,arg2)
返回值:arg1除于arg2的结果
*/
Div: function (arg1, arg2) {
var r1 = arg1.toString(), r2 = arg2.toString(), m, resultVal, d = arguments[2];
m = (r2.split(".")[1] ? r2.split(".")[1].length : 0) - (r1.split(".")[1] ? r1.split(".")[1].length : 0);
resultVal = Number(r1.replace(".", "")) / Number(r2.replace(".", "")) * Math.pow(10, m);
return typeof d !== "number" ? Number(resultVal) : Number(resultVal.toFixed(parseInt(d)));
}
};
window.Calc = calc;