- 1.
JavaScript
规定了几种语言类型 - 2.
JavaScript
对象的底层数据结构是什么 - 3.
Symbol
类型在实际开发中的应用、可手动实现一个简单的Symbol
- 4.
JavaScript
中的变量在内存中的具体存储形式 - 5.基本类型对应的内置对象,以及他们之间的装箱拆箱操作
- 6.理解值类型和引用类型
- 7.
null
和undefined
的区别 - 8.至少可以说出三种判断
JavaScript
数据类型的方式,以及他们的优缺点,如何准确的判断数组类型 - 9.可能发生隐式类型转换的场景以及转换原则,应如何避免或巧妙应用
- 10.出现小数精度丢失的原因,
JavaScript
可以存储的最大数字、最大安全数字,JavaScript
处理大数字的方法、避免精度丢失的方法
1.JavaScript规定了几种语言类型
JavaScript中的每一个值都有它自己的类型,JavaScript规定了七种语言类型:
- Undefined:未定义,声明了变量但是没有赋值
var m;
console.log(s); //undefined ,如果一个没有声明的变量,会报错
- Null:空
var m=null;
console.log(m); //null
console.log(typeof(m)); //object
//咦?为什么是object?这实际上是涉及到JavaScript内部的存储机制,js中的数据在底层是以二进制存储
//如果前三位为0,那么就会判定为object,而null的所有都为0,自然也就是object类型了,记住就好了,特殊情况特殊处理。
- Boolean:布尔值(true/false)
var m=true;
console.log(typeof(m)); //boolean
- String :表示字符串类型
var test0='test';
console.log(typeof(test0));//返回 string
var test1=new String('test');
console.log(typeof(test1));//返回 object
- Number:表示数字
var m=1;
console.log(typeof(m)); //输出 number
- Symbol:(在 ECMAScript 6 中新添加的类型))一种数据类型,它的实例是唯一且不可改变的
var s=Symbol('添加描述');
console.log(s); //Symbol(添加描述)
console.log(typeof(s)); //symbol
let s1 = Symbol('foo'); let s2 = Symbol('foo');
s1 === s2 // false
- Object
var obj={};
function fun(){};
var arr=[];
var date=new Date();
var er=new Error('ssss');
console.log(typeof(obj));//object
console.log(typeof(fun));//function
console.log(typeof(fun.prototype));//object
console.log(typeof(arr));//object
console.log(typeof(date));//object
console.log(er);//Error:ssss
2. JavaScript对象的底层数据结构是什么
引擎层的 Object 类,然后 HeapObject 往下派生,每个 heap object 都有个 map 来记录相关信息。
所有js对象都是基于一个名为HeapObject的类生成的。生成对象的时候,引擎通过这个类向Heap申请空间。这个分配过程基本都是引擎自己实现,而不会调用malloc,因为要实现精确GC
3. Symbol类型在实际开发中的应用、可手动实现一个简单的Symbol
- 场景一:使用Symbol来作为对象属性名key
特性:Symbol类型的key是不能通过Object.keys()或者for...in来枚举的,它未被包含在对象自身的属性名集合(property names)之中。所以,利用该特性,我们可以把一些不需要对外操作和访问的属性使用Symbol来定义。也正因为这样一个特性,当使用JSON.stringify()将对象转换成JSON字符串的时候,Symbol属性也会被排除在输出内容之外。
let obj = {
[Symbol('name')]: 'zzz',
age: 18,
title: 'Engineer'
}
Object.keys(obj) // ['age', 'title']
for (let p in obj) {
console.log(p) // 分别会输出:'age' 和 'title'
}
Object.getOwnPropertyNames(obj) // ['age', 'title']
JSON.stringify(obj) // {"age":18,"title":"Engineer"}
好处:我们可以利用这一特点来更好的设计我们的数据对象,让“对内操作”和“对外选择性输出”变得更加优雅
会有一些专门针对Symbol的API获取以Symbol方式定义的对象属性
// 使用Object的API
Object.getOwnPropertySymbols(obj) // [Symbol(name)]
// 使用新增的反射API
Reflect.ownKeys(obj) // [Symbol(name), 'age', 'title']
- 场景二:使用Symbol代替常量
/*
const TYPE_AUDIO = 'AUDIO'
const TYPE_VIDEO = 'VIDEO'
const TYPE_IMAGE = 'IMAGE'
*/
const TYPE_AUDIO = Symbol()
const TYPE_VIDEO = Symbol()
const TYPE_IMAGE = Symbol()//保证变量的唯一性,但是这种场景意义不是很大,少了一些特定变量名称的粘贴复制吧,const声明的就是唯一值了
function handleFileResource(resource) {
switch(resource.type) {
case TYPE_AUDIO:
playAudio(resource)
break
case TYPE_VIDEO:
playVideo(resource)
break
case TYPE_IMAGE:
previewImage(resource)
break
default:
throw new Error('Unknown type of resource')
}
}
- 场景三:使用Symbol定义类的私有属性/方法
在JavaScript中,是没有如Java等面向对象语言的访问控制关键字private的,类上所有定义的属性或方法都是可公开访问的。因此这对我们进行API的设计时造成了一些困扰。
而有了Symbol以及模块化机制,类的私有属性和方法才变成可能。例如:
在文件 a.js中:
const PASSWORD = Symbol()
class Login {
constructor(username, password) {
this.username = username
this[PASSWORD] = password
}
checkPassword(pwd) {
return this[PASSWORD] === pwd
}
}
export default Login
在文件b中:
import Login from './a'
const login = new Login('admin', '123456')
login.checkPassword('123456') // true
login.PASSWORD // oh!no!
login[PASSWORD] // oh!no!
login["PASSWORD"] // oh!no!
由于Symbol常量PASSWORD被定义在a.js所在的模块中,外面的模块获取不到这个Symbol,也不可能再创建一个一模一样的Symbol出来(因为Symbol是唯一的),因此这个PASSWORD的Symbol只能被限制在a.js内部使用,所以使用它来定义的类属性是没有办法被模块外访问到的,达到了一个私有化的效果。
- 场景四:注册和获取全局Symbol
通常情况下,我们在一个浏览器窗口中(window),使用Symbol()函数来定义和Symbol实例就足够了。但是,如果你的应用涉及到多个window(最典型的就是页面中使用了<iframe>),并需要这些window中使用的某些Symbol是同一个,那就不能使用Symbol()函数了,因为用它在不同window中创建的Symbol实例总是唯一的,而我们需要的是在所有这些window环境下保持一个共享的Symbol。这种情况下,我们就需要使用另一个API来创建或获取Symbol,那就是Symbol.for(),它可以注册或获取一个window间全局的Symbol实例
let gs1 = Symbol.for('global_symbol_1') //注册一个全局Symbol
let gs2 = Symbol.for('global_symbol_1') //获取全局Symbol
gs1 === gs2 // true
4. JavaScript中的变量在内存中的具体存储形式
栈内存和堆内存
JavaScript中的变量分为基本类型和引用类型
基本类型是保存在栈内存中的简单数据段,它们的值都有固定的大小,保存在栈空间,通过按值访问
引用类型是保存在堆内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,JavaScript不允许直接访问堆内存中的位置,因此操作对象时,实际操作对象的引用
let a1 = 0; // 栈内存
let a2 = "this is string" // 栈内存
let a3 = null; // 栈内存
let b = { x: 10 }; // 变量b存在于栈中,{ x: 10 }作为对象存在于堆中
let c = [1, 2, 3]; // 变量c存在于栈中,[1, 2, 3]作为对象存在于堆中
栈内存 | 堆内存 |
---|---|
存储基础数据类型 | 存储引用数据类型 |
按值访问 | 按引用访问 |
存储的值大小固定 | 存储的值大小不定,可动态调整 |
由系统自动分配内存空间 | 由代码进行指定分配 |
空间小,运行效率高 | 空间大,运行效率相对较低 |
先进后出,后进先出 | 无序存储,可根据引用直接获取 |
5. 基本类型对应的内置对象,以及他们之间的装箱拆箱操作
基本类型的内置对象有String()
、Number()
、Boolean()
、Symbol()
引用类型的内置对象有RegExp()
、Date()
、Array()
、Object()
、Function()
、Error()
、
装箱操作:隐式转换 值类型 =》 对象类型
拆箱操作:显式转换 对象类型 =》 值类型
装箱,就是把基本类型转变为对应的对象。装箱分为隐式和显示。
隐式装箱
每当读取一个基本类型的值时,后台会创建一个该基本类型所对应的对象。在这个基本类型上调用方法,其实是在这个基本类型对象上调用方法。这个基本类型的对象是临时的,它只存在于方法调用那一行代码执行的瞬间,执行方法后立刻被销毁。
num.toFixed(2); // '123.00'
//上方代码在后台的真正步骤为
var c = new Number(123);//创建一个 Number 类型的实例。
c.toFixed(2);//在实例上调用方法。
c = null;//销毁实例
显式装箱
通过内置对象 Boolean、Object、String 等可以对基本类型进行显示装箱。
var obj = new String('123');
拆箱。拆箱与装箱相反,把对象转变为基本类型的值。拆箱过程内部调用了抽象操作 ToPrimitive 。该操作接受两个参数,第一个参数是要转变的对象,第二个参数 PreferredType 是对象被期待转成的类型。第二个参数不是必须的,默认该参数为 number,即对象被期待转为数字类型。有些操作如 String(obj) 会传入 PreferredType 参数。有些操作如 obj + " " 不会传入 PreferredType
具体转换过程是这样的。默认情况下,ToPrimitive 先检查对象是否有 valueOf 方法,如果有则再检查 valueOf 方法是否有基本类型的返回值。如果没有 valueOf 方法或 valueOf 方法没有返回值,则调用 toString 方法。如果 toString 方法也没有返回值,产生 TypeError 错误。
PreferredType 影响 valueOf 与 toString 的调用顺序。如果 PreferrenType 的值为 string。则先调用 toString ,再调用 valueOf。
var obj = {
valueOf : () => {console.log("valueOf"); return []},
toString : () => {console.log("toString"); return []}
}
String(obj)
// toString
// valueOf
// Uncaught TypeError: Cannot convert object to primitive value
obj+' '
//valueOf
//toString
// Uncaught TypeError: Cannot convert object to primitive value
Number(obj)
//valueOf
//toString
// Uncaught TypeError: Cannot convert object to primitive value
6. 理解值类型和引用类型
基本类型的内置对象有String()
、Number()
、Boolean()
、Symbol()
引用类型的内置对象有RegExp()
、Date()
、Array()
、Object()
、Function()
、Error()
、
值类型
1、占用空间固定,保存在栈中(当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁了。因此,所有在方法中定义的变量都是放在栈内存中的;栈中存储的是基础变量以及一些对象的引用变量,基础变量的值是存储在栈中,而引用变量存储在栈中的是指向堆中的数组或者对象的地址,这就是为何修改引用类型总会影响到其他指向这个地址的引用变量。)
2、保存与复制的是值本身
3、使用typeof检测数据的类型
4、基本类型数据是值类型引用类型
1、占用空间不固定,保存在堆中(当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(方法的参数传递时很常见),则这个对象依然不会被销毁,只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。)
2、保存与复制的是指向对象的一个指针
3、使用instanceof检测数据类型
4、使用new()方法构造出的对象是引用型
7. null和undefined的区别
null == undefined // true
null === undefined // false
!!null // false
!!undefined // false
Number(null) //0
Number(undefined) //NaN
/*null更多表示引用语义, 它表示一个值被定义了,定义为“空值”。
undefined更多表示值语义,它表示根本不存在定义。
所以设置一个值为null是合理的,但是设置一个值为undefined就不合理。*/
typeof null // object
typeof undefined // undefined
8. 至少可以说出三种判断JavaScript数据类型的方式,以及他们的优缺点,如何准确的判断数组类型
判断数据类型的方法一般可以通过:typeof、instanceof、constructor、Object.prototype.toString.call();四种常用方法
- typeof
typeof 返回一个表示数据类型的字符串,返回结果包括:number、boolean、string、object、undefined、function、Symbol6种数据类型。
对于引用类型,返回的都是object,其实返回object也没有错,因为所有对象的原型链最终都指向了Object,Object是所有对象的祖宗
。 但当我们需要知道某个对象的具体类型时,typeof 就显得有些力不从心了。
注意:typeof null也是返回object
- instanceof
判断对象和构造函数在原型链上是否有关系,如果有关系,返回真,否则返回假.
function Aaa(){
}
var a1 = new Aaa();
console.log( a1 instanceof Aaa);
//true 判断a1和Aaa是否在同一个原型链上,是的话返回真,否则返回假
var arr = [];
console.log( arr instanceof Aaa);//false
var str = 'hello';
alert(str instanceof String);//false
var bool = true;
alert(bool instanceof Boolean);//false
var num = 123;
alert(num instanceof Number);//false
var nul = null;
alert(nul instanceof Object);//false
var und = undefined;
alert(und instanceof Object);//false
var oDate = new Date();
alert(oDate instanceof Date);//true
var json = {};
alert(json instanceof Object);//true
var arr = [];
alert(arr instanceof Array);//true
var reg = /a/;
alert(reg instanceof RegExp);//true
var fun = function(){};
alert(fun instanceof Function);//true
var error = new Error();
alert(error instanceof Error);//true
从上面的运行结果我们可以看到,基本数据类型是没有检测出他们的类型,但是我们使用下面的方式创建num、str、boolean,是可以检测出类型的:
var num = new Number(123);
var str = new String('abcdef');
var boolean = new Boolean(true);
console.log(num instanceof Number)//true
console.log(str instanceof String)//true
- constructor:查看对象对应的构造函数
constructor 在其对应对象的原型下面,是自动生成的。当我们写一个构造函数的时候,程序会自动添加:构造函数名.prototype.constructor = 构造函数名
function Aaa(){
}
//Aaa.prototype.constructor = Aaa; //每一个函数都会有的,都是自动生成的
判断数据类型的方法
var str = 'hello';
alert(str.constructor == String);//true
var bool = true;
alert(bool.constructor == Boolean);//true
var num = 123;
alert(num.constructor ==Number);//true
// var nul = null;
// alert(nul.constructor == Object);//报错
//var und = undefined;
//alert(und.constructor == Object);//报错
var oDate = new Date();
alert(oDate.constructor == Date);//true
var json = {};
alert(json.constructor == Object);//true
var arr = [];
alert(arr.constructor == Array);//true
var reg = /a/;
alert(reg.constructor == RegExp);//true
var fun = function(){};
alert(fun.constructor ==Function);//true
var error = new Error();
alert(error.constructor == Error);//true
从上面的测试中我们可以看到,undefined和null是不能够判断出类型的,并且会报错。因为null和undefined是无效的对象,因此是不会有constructor存在的
同时我们也需要注意到的是:使用constructor是不保险的,因为constructor属性是可以被修改的,会导致检测出的结果不正确
备注:使用Object.create()创建的js对象,没有constructor
- Object.prototype.toString(可以说不管是什么类型,它都可以立即判断出)
toString是Object原型对象上的一个方法,该方法默认返回其调用者的具体类型,更严格的讲,是 toString运行时this指向的对象类型, 返回的类型
格式为[object xxx],xxx是具体的数据类型,其中包括:
String,Number,Boolean,Undefined,Null,Function,Date,Array,RegExp,Error,HTMLDocument,... 基本上所有对象的类型都可以通过这个方法获取到。
var str = 'hello';
console.log(Object.prototype.toString.call(str));//[object String]
var bool = true;
console.log(Object.prototype.toString.call(bool))//[object Boolean]
var num = 123;
console.log(Object.prototype.toString.call(num));//[object Number]
var nul = null;
console.log(Object.prototype.toString.call(nul));//[object Null]
var und = undefined;
console.log(Object.prototype.toString.call(und));//[object Undefined]
var oDate = new Date();
console.log(Object.prototype.toString.call(oDate));//[object Date]
var json = {};
console.log(Object.prototype.toString.call(json));//[object Object]
var arr = [];
console.log(Object.prototype.toString.call(arr));//[object Array]
var reg = /a/;
console.log(Object.prototype.toString.call(reg));//[object RegExp]
var fun = function(){};
console.log(Object.prototype.toString.call(fun));//[object Function]
var error = new Error();
console.log(Object.prototype.toString.call(error));//[object Error]
类型 | typeof | instanceof | constructor | Object.prototype.toString.call |
---|---|---|---|---|
优点 | 使用简单 | 能检测出引用类型 | 基本能检测所有的类型(除了null和undefined) | 检测出所有类型 |
缺点 | 只能检测出基本类型(出null) | 能检测出基本类型,且不能跨iframe | constructor易被修改,也不能跨iframe IE6下,undefined和null均为Object |
例:跨页面判断是否是数组
window.onload = function(){
var oF = document.createElement('iframe');
document.body.appendChild( oF );
var ifArray = window.frames[0].Array;
var arr = new ifArray();
//alert( arr.constructor == Array ); //false
//alert( arr instanceof Array ); //false
alert( Object.prototype.toString.call(arr) == '[object Array]' ); //true
};
9. 可能发生隐式类型转换的场景以及转换原则,应如何避免或巧妙应用
js数据类型隐式转换主要分为三种情况
1.转换为string
2.转换为number
3.转换为boolean
原始值 | 转换目标 | 结果 |
---|---|---|
number | 布尔值 | 除了0,-0,NaN都为true |
string | 布尔值 | 除了空字符串都为true |
defined、null | 布尔值 | false |
引用类型 | 布尔值 | false |
number | 字符串 | 5=>'5' |
Boolean、函数、Symbol | 字符串 | 'true' |
数组 | 字符串 | [1,2]=>'1,2' |
对象 | 字符串 | '[object object]' |
string | 数字 | '1'=>1,'a'=>NaN |
数组 | 数字 | 空数组为0,存在一个元素且为数字转数字,其他情况NaN |
null | 数字 | 0 |
除了数组的引用类型 | 数字 | NaN |
Symbol | 数字 | 抛错 |
隐式类型转换。Javascript默认自动转换,没有任何警告,隐式类型转换常见场景
- 自动转换Boolean
例如if语句或者其他需要Boolean的地方
if (表达式){}
- 在非 Numeber 类型进行数学运算符 - * / 时,会先将非 Number 转换成 Number 类型。
+
运算符要考虑字符串的情况,在操作数中存在字符串时,优先转换成字符串
+
运算符其中一个操作数是字符串的话,会进行连接字符串的操作
1+'2' // '12'
+
操作符的执行顺序是:
当一侧操作数为 String 类型,会优先将另一侧转换为字符串类型。
当一侧操作数为 Number 类型,另一侧为原始类型,则将原始类型转换为 Number 类型。
当一侧操作数为 Number 类型,另一侧为引用类型,将引用类型和 Number 类型转换成字符串后拼接。
- 对象。只有在 JavaScript 表达式或语句中需要用到数字或字符串时,对象才被隐式转换。
当需要将对象转换为数字时,需要三个步骤
3*{valueOf:function () { return 5 }} // 15,调用 valueOf()。如果结果是原始值(不是一个对象),则将其转换为一个数字。
3*{toString:function () { return 5 }} // 15,否则,调用 toString() 方法。如果结果是原始值,则将其转换为一个数字
3*{toString:function () { return {} }} //TypeError: Cannot convert object to primitive value
//否则,抛出一个类型错误。
10. 出现小数精度丢失的原因,JavaScript可以存储的最大数字、最大安全数字,JavaScript处理大数字的方法、避免精度丢失的方法
var x = 0.3 - 0.2; //30美分减去20美分
var y = 0.2 - 0.1; //20美分减去10美分
x == y; // =>false,两值不相等
x == 0.1; // =>false,真实值为:0.09999999999999998
y == 0.1; // =>true
这个问题并不只是在Javascript中才会出现,任何使用二进制浮点数的编程语言都会有这个问题,只不过在 C++/C#/Java 这些语言中已经封装好了方法来避免精度的问题,而 JavaScript 是一门弱类型的语言,从设计思想上就没有对浮点数有个严格的数据类型,所以精度误差的问题就显得格外突出
解决办法
//加法
Number.prototype.add = function(arg) {
var r1, r2, m;
try {
r1 = this.toString().split(".")[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = arg.toString().split(".")[1].length;
} catch (e) {
r2 = 0;
}
m = Math.pow(10, Math.max(r1, r2));
return (this * m + arg * m) / m;
};
//减法
Number.prototype.sub = function(arg) {
return this.add(-arg);
};
//乘法
Number.prototype.mul = function(arg) {
var m = 0,
s1 = this.toString(),
s2 = arg.toString();
try {
m += s1.split(".")[1].length;
} catch (e) {}
try {
m += s2.split(".")[1].length;
} catch (e) {}
return (
(Number(s1.replace(".", "")) * Number(s2.replace(".", ""))) /
Math.pow(10, m)
);
};
//除法
Number.prototype.div = function(arg) {
var t1 = 0,
t2 = 0,
r1,
r2;
try {
t1 = this.toString().split(".")[1].length;
} catch (e) {}
try {
t2 = arg.toString().split(".")[1].length;
} catch (e) {}
with (Math) {
r1 = Number(this.toString().replace(".", ""));
r2 = Number(arg.toString().replace(".", ""));
return (r1 / r2) * pow(10, t2 - t1);
}
};
参考链接:https://blog.csdn.net/Donspeng/java/article/details/76019815
https://www.jianshu.com/p/f40a77bbd74e
https://www.jianshu.com/p/80bb5a01857a
https://blog.csdn.net/xieamy/java/article/details/89892451
https://www.jianshu.com/p/d66cf6f711a1
https://blog.csdn.net/weixin_43618932/java/article/details/103109718
https://juejin.im/post/5cda9178f265da0379419ad1