前端知识体系1:JavaScript基础-变量和类型

前端工程师自检清单

 

1. JavaScript规定了几种语言类型

2. JavaScript对象的底层数据结构是什么

3. Symbol类型在实际开发中的应用、可手动实现一个简单的 Symbo

4. JavaScript中的变量在内存中的具体存储形式

5.基本类型对应的内置对象,以及他们之间的装箱拆箱操作

6.理解值类型和引用类型

7. null和 undefined的区别

8.至少可以说出三种判断 JavaScript数据类型的方式,以及他们的优缺点,如何准确的判断数组类型

9.可能发生隐式类型转换的场景以及转换原则,应如何避免或巧妙应用

10.出现小数精度丢失的原因, JavaScript可以存储的最大数字、最大安全数字, JavaScript处理大数字的方法、避免精度丢失的方法

1. JavaScript规定了几种语言类型(推荐阅读 https://www.cnblogs.com/onepixel/p/5140944.html

答:JavaScript有7大基本类型。(6种原始类型,1种引用类型) https://www.cnblogs.com/memphis-f/p/11913756.html

  1)、Undefined

    undefined表示未定义,它的值只有一个:undefined。当声明的变量未初始化时,变量的默认值是undefined

    任何变量赋值前都是undefined类型,值为undefined而不是null,undefined是一个变量而不是一个关键字。

    我们一般不会把变量赋值为undefined,这样可以保证所有值为undefined的变量,都是从未赋值的自然变量。

  2)、Null

    只有一个值就是null,表示空值,是关键字。

    null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象

  3)、Boolean  

Boolean(true) // true
Boolean(false) // false
Boolean('Hello Wolrd') // true
Boolean() // false
Boolean('') // false
Boolean(' ') // true (里面有空格)
Boolean(1) // true
Boolean(0) // false
Boolean(NaN) // false
Boolean({}) // true
Boolean([]) // true
Boolean(null) // false
Boolean(undefined) // false

 

    把其他类型转换为Boolean类型有三种方式:

    1)Boolean()

    2)! 或者 !!,取反,先转为Boolean然后再取反

    3)条件判断    

  4)、String

    字符串是存储字符的变量。

    在JS中的字符串需要用引号引起来(英文单引号或者双引号)

    String对象方法:

    1)indexOf()

      该方法可返回某个指定字符串值在字符串中首次出现的位置。并返回第一次出现的位置。如果要检索的字符串值没有出现,则返回-1

    2)lastIndexOf()

      该方法可返回某个指定字符串值在字符串中最后出现的位置,在一个字符串中的指定位置从后向前检索。如果要检索的字符串值没有出现,则返回-1

    3)concat()

      该方法用于连接两个或多个字符串。使用 + 号运算符来进行字符串的连接运算通常会更简便些。

    4)slice()

      该方法可提取字符串的某个部分,并以返回被提取的部分

    5)toLowerCase()

      该方法用于将字符串转换为小写

    6)toUpperCase()

      该方法用于将字符串转换为大写

  5)、Number

    Number是与数字值对应的引用类型

    常用方法:

    1)toFixed()

      方法会按照指定的小数位返回数值的字符串表示

    2)toExponential()

      该方法返回以指数表示法(也称 e 表示法)表示的数值的字符串形式

    3)toPrecision()

      方法可能会返回固定大小(fixed)格式,也可能返回指数(exponential)格式;具体规则是看哪种格式最合适。

      这个方法接收一个参数,即表示数值的所有数字的位数(不包括指数部分)。

var num = 10; 
alert(num.toFixed(2)); //"10.00" 

var num = 10; 
alert(num.toExponential(1)); //"1.0e+1" 

var num = 99; 
alert(num.toPrecision(1)); //"1e+2" 
alert(num.toPrecision(2)); //"99" 
alert(num.toPrecision(3)); //"99.0" 

 

  6)、Symbol

    表示独一无二的值,它是一切非字符串的对象key的集合。 Symbol 值通过Symbol函数生成。

    这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。

    凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。 

    Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,但是即使描述相同,Symbol值也不相等。

 

let s1 = Symbol('foo'); let s2 = Symbol('foo');

s1 === s2 // false

一些标准中提到的 Symbol,可以在全局的 Symbol 函数的属性中找到。例如,我们可以使用 Symbol.iterator 来自定义 for…of 在对象上的行为: var o = new Object

o[Symbol.iterator] = function() {
    var v = 0
    return {
        next: function() {
            return { value: v++, done: v > 10 }
        }
    }        
};
​
for(var v of o) 
    console.log(v); // 0 1 2 3 ... 9

 

  7)、Object

    对象是某个特定引用类型的实例。在ECMAScript中,object类型是所有它的实例的基础。换句话说,Object类型所具有的任何属性和方法也同样存在于更具体的对象中。
    Object的每个实例都具有下列的属性和方法:

  • [x] constructor: 构造函数
  • [x] hasOwnProperty(propertyName)
    用于检查给定的属性在当前对象实例(而不是实例的原型)中是否存在。

  • [x] isPrototypeOf(Object):
    用于检查其原型链的对象是否存在于指定对象的实例中,是则返回true,否则返回false。
    例如:

    var a = {}
    function Person() {}
    var p1 = new Person() // 继承自原来的原型,但现在已经无法访问
    var Person.prototype = a
    var p2 = new Person() // 继承a
    
    console.log(a.isPrototypeOf(p1)) // false  a是不是p1的原型
    console.log(a.isPrototypeOf(p2)) // true  a是不是p2的原型
    
    console.log(Object.prototype.isPrototypeOf(p1)) // true
    console.log(Object.prototype.isPrototypeOf(p2)) // true
  • [x] propertyIsEnumerable(propertyName)
    用于检查给定的属性是否可以用 for-in 语句进行枚举。
  • [x] toLocaleString()
    返回对象的字符串表示,该字符串与执行环境的地区对应。
  • [x] toString()
    返回对象的字符串表示。
  • [x] valueOf()
    返回对象的字符串、数值、布尔值表示。通常与toString()方法的返回值相同。
    创建Object实例的方法:
    1、第一种是使用new操作符后跟Object构造函数。
    var person=new Object();
    person.name="Nicholas";
    person.age=29;

    2、使用对象字面量。对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。

    var person={
        name:"Nicholas",
        age:29
    };

2. JavaScript对象的底层数据结构是什么
参考文章:再谈JS 对象数据结构底层实现原理

     从Chrome源码看JS Object的实现

3. Symbol类型在实际开发中的应用、可手动实现一个简单的 Symbol(参考文档

答:Symbol是由ES6规范引入的一项新特性,它的功能类似于一种标识唯一性的ID。每个Symbol实例都是唯一的。

  因此,当你比较两个Symbol实例的时候,将总会返回false

let s1 = Symbol()
let s2 = Symbol('another symbol')
let s3 = Symbol('another symbol')
s1 === s2 // false
s2 === s3 // false

  应用场景1:使用symbol来作为对象属性名(key)

// ① 通常我们定义或访问对象的属性时都是使用字符串
let obj = {
  abc: 123,
  "hello": "world"
}
obj["abc"] // 123
obj["hello"] // 'world'
// ② 现在symbol同样可以用于对象属性的定义和访问
const PROP_NAME = Symbol()
const PROP_AGE = Symbol()
let obj = {
  [PROP_NAME]: "一斤代码"
}
obj[PROP_AGE] = 18
obj[PROP_NAME] // '一斤代码'
obj[PROP_AGE] // 18
// ③ Symbol类型的key是不能通过Object.keys()或者for...in来枚举的,它未被包含在对象自身的属性名集合(property names)之中。所以,利用该特性,我们可以把一些不需要对外操作和访问的属性使用Symbol来定义。如下:
let obj = {
   [Symbol('name')]: '一斤代码',
   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()将对象转换成JSON字符串的时候,Symbol属性也会被排除在输出内容之外:
JSON.stringify(obj) // {"age":18,"title":"Engineer"}
// ⑤ 我们可以利用这一特点来更好的设计我们的数据对象,让“对内操作”和“对外选择性输出”变得更加优雅。
// ⑥ 获取以symbol方式定义对象属性的API // 使用Object的API Object.getOwnPropertySymbols(obj) // [Symbol(name)] // 使用新增的反射API Reflect.ownKeys(obj) // [Symbol(name), 'age', 'title']

  应用场景2:使用symbol来代替常量

// 我们经常定义一组常量来代表一种业务逻辑下的几个不同类型,我们通常希望这几个常量之间是唯一的关系,为了保证这一点,我们需要为常量赋一个唯一的值(比如这里的'AUDIO'、'VIDEO'、 'IMAGE'),
常量少的时候还算好,但是常量一多,你可能还得花点脑子好好为他们取个好点的名字。如下:
const TYPE_AUDIO = 'AUDIO' const TYPE_VIDEO = 'VIDEO' const TYPE_IMAGE = 'IMAGE' 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,我们大可不必这么麻烦了,这样定义,直接就保证了三个常量的值是唯一的了: const TYPE_AUDIO = Symbol() const TYPE_VIDEO = Symbol() const TYPE_IMAGE = Symbol()

  应用场景3:使用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.js 中:
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内部使用,所以使用它来定义的类属性是没有办法被模块外访问到的,达到了一个私有化的效果。

4. JavaScript中的变量在内存中的具体存储形式(参考文档

答:JavaScript中的变量分为基本类型和引用类型

  基本类型是保存在栈内存中的简单数据段,它们的值都有固定的大小,保存在栈空间,通过按值访问

  引用类型是保存在堆内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,JavaScript不允许直接访问堆内存中的位置,因此操作对象时,实际操作对象的引用

  基本类型发生复制行为: 在栈内存中的数据发生复制行为时,系统会自动为新的变量分配一个新值,最后这些变量都是相互独立互不影响的  
  引用类型发生复制行为:
  • 引用类型的复制,同样为新的变量b分配一个新的值,保存在栈内存中,不同的是,这个值仅仅是引用类型的一个地址指针
  • 他们两个指向同一个值,也就是地址指针相同,在堆内存中访问到的具体对象实际上是同一个
  • 因此改变b.x时,a.x也发生了变化,这就是引用类型的特性
  
  总结:
  

5. 基本类型对应的内置对象,以及他们之间的装箱拆箱操作

答:基本类型对应的内置对象:String()、Number()、Boolean()、RegExp()、Date()、Error()、Array()、

  Function()、Object()、symbol();类似于对象的构造函数

  1、这些内置函数构造的变量都是封装了基本类型值的对象如:

  Var a=new String(‘abb’); //typeof(a)=object

  除了利用Function()构造的变量通过typeof输出为function外其他均为object

  2、为了知道构造的变量的真实类型可以利用:

  Object.prototype.toString.call([1,2,3]);//”[object,array]”,后面的一个值即为传入参数的类型

  3、如果有常量形式(即利用基本数据类型)赋值给变量就不要用该方式来定义变量

  一、装箱

  所谓装箱,就是把基本类型转变为对应的对象。装箱分为隐式和显式:

  隐式

// 每当读取一个基本类型的值时,后台会创建一个该基本类型所对应的对象。在这个基本类型上调用方法,其实是在这个基本类型对象上调用方法。这个基本类型的对象是临时的,它只存在于方法调用那一行代码执行的瞬间,执行方法后立刻被销毁。具体到代码如下:
    num.toFixed(2); // '123.00'
    // 上方代码在后台的真正步骤为
    var c = new Number(123);
    c.toFixed(2);
    c = null;
// 当我们访问 num 时,要从内存中读取这个数字的值,此时访问过程处于读取模式。在读取模式中,后台进行了三步处理:
/**
1、创建一个 Number 类型的实例。
2、在实例上调用方法。
3、销毁实例。
**/

  显式

// 通过内置对象 Boolean Object String等可以对基本类型进行显式装箱
var obj = new String('123');

  二、拆箱

  拆箱与装箱相反,把对象转变为基本类型的值。

// 拆箱过程内部调用了抽象操作ToPrimitive:
// 该操作接受两个参数,第一个参数是要转变的对象,第二个参数 PreferredType 是对象被期待转成的类型。
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. 理解值类型和引用类型

答:JavaScript中的变量类型:

  (1) 值类型(基本类型):字符串(string)、数值(number)、布尔值(boolean)、undefined、null  (这5种基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值)

    (ECMAScript 2016新增了一种基本数据类型:symbol http://es6.ruanyifeng.com/#docs/symbol )

  (2) 引用类型:对象(Object)、数组(Array)、函数(Function)

  值类型和引用类型的区别

  (1) 值类型:

      1、占用空间固定,保存在栈中

         (当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁了。

          因此,所有在方法中定义的变量都是放在栈内存中的;

          栈中存储的是基础变量以及一些对象的引用变;

          基础变量的值是存储在栈中;

          而引用变量存储在栈中的是指向堆中的数组或者对象的地址。这就是为何修改引用类型总会影响到其他指向这个地址的引用变量。)

      2、保存与复制的是值本身 

      3、使用typeof检测数据的类型

                  4、基本类型数据是值类型

  (2) 引用类型:

      1、占用空间不固定,保存在堆中

        (当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。

         堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(方法的参数传递时很常见),

         则这个对象依然不会被销毁,只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。)

                  2、保存与复制的是指向对象的一个指针

                  3、使用instanceof检测数据类型

                  4、使用new()方法构造出的对象是引用型

    

7. null和 undefined的区别

答:null:Null类型,代表 “空值”,代表一个空对象指针,使用typeof运算得到 “object” ,所以可以认为它是一个特殊的对象值。表示"没有对象",即该处不应该有值。用法:

 
 

  Object.getPrototypeOf(Object.prototype)
  // null

(1) 作为函数的参数,表示该函数的参数不是对象。
(2) 作为对象原型链的终点。

  undefined:Undefined类型只有一个值,即undefined。表示"缺少值",就是此处应该有一个值,但是还没有定义。用法:

(1)变量被声明了,但没有赋值时,就等于undefined。(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。(3)对象没有赋值的属性,该属性的值为undefined。(4)函数没有返回值时,默认返回undefined。
知识了解:
undefined与null的区别,这与JavaScript的历史有关。1995年JavaScript诞生时,最初像Java一样,只设置了null作为表示"无"的值。 根据C语言的传统,null被设计成可以自动转为0。 Number(null) // 0 5 + null // 5 但是,JavaScript的设计者Brendan Eich,觉得这样做还不够,有两个原因。 首先,null像在Java里一样,被当成一个对象。但是,JavaScript的数据类型分成原始类型(primitive)和合成类型(complex)两大类,Brendan Eich觉得表示"无"的值最好不是对象。 其次,JavaScript的最初版本没有包括错误处理机制,发生数据类型不匹配时,往往是自动转换类型或者默默地失败。Brendan Eich觉得,如果null自动转为0,很不容易发现错误。 因此,Brendan Eich又设计了一个undefined。

8. 至少可以说出三种判断 JavaScript数据类型的方式,以及他们的优缺点,如何准确的判断数组类型

答:判断数据类型的方法一般可以通过:typeof、instanceof、constructor、toString四种常用方法:

  1、typeof (可以对基本类型做出准确的判断,但对于引用类型,用它就有点力不从心了)

    typeof返回一个表示数据类型的字符串,返回结果包括:number、boolean、string、object、undefined、function等6种数据类型。

    typeof 可以对JS基本数据类型做出准确的判断(除了null),而对于引用类型返回的基本上都是object,。

    其实返回object也没有错,因为所有对象的原型链最终都指向了Object,Object是所有对象的`祖宗`。 但当我们需要知道某个对象的具体类型时,typeof 就显得有些力不从心了。

    注意:typeof  null会返回object,因为特殊值null被认为是一个空的对象引用

  2、instanceof

    判断对象和构造函数在原型链上是否有关系。有关系返回真,否则返回假。

function Aaa(){}
var a1 = new Aaa();
//alert( a1 instanceof Aaa);  //true  判断a1和Aaa是否在同一个原型链上,是的话返回真,否则返回假
var arr = [];
alert( 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);
alert(num instanceof Number);//true
var str = new String('abcdef');
alert(str instanceof String);//true
var boolean = new Boolean(true);
alert(boolean instanceof Boolean;//true

  3、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属性是可以被修改的,会导致检测出的结果不正确
function Aaa(){}
Aaa.prototype.constructor = Aaa;//程序可以自动添加,当我们写个构造函数的时候,程序会自动添加这句代码
function BBB(){}
Aaa.prototype.constructor = BBB;//此时我们就修改了Aaa构造函数的指向问题
alert(Aaa.construtor==Aaa);//false
// 可以看出,constructor并没有正确检测出正确的构造函数

  4、Object.prototype.toString()

    toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。

    对于 Object 对象,直接调用 toString()  就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。

    判断类型example:

Object.prototype.toString.call('') ;   // [object String]
Object.prototype.toString.call(1) ;    // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(Symbol()); //[object Symbol]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object global] window 是全局对象 global 的引用

9. 可能发生隐式类型转换的场景以及转换原则,应如何避免或巧妙应用

答:1、运算符转换

    -,*,/,%会将操作数转换为数字去计算,但+不一样,两边纯数字会按数字相加,纯字符串会拼接,但数字和字符串也会将字符串和数字拼接起来。

console.log("1 - '2'");
console.log(1 - '2'); //-1
console.log("1 * '2'");
console.log(1 * '2'); //2
console.log("6 / '4'");
console.log(6 / '4'); //1.5
console.log("6 % '4'");
console.log(6 % '4'); //2    

console.log("6 + 4");
console.log(6 + 4); //10
console.log("6 + '4'");
console.log(6 + '4'); //64
console.log("'6' + '4'");
console.log('6' + '4'); //64
console.log("typeof'6' + '4'");
console.log(typeof('6' + '4')); //string

  2、双等号转换

    两边会转换为同一类型再进行比较。双等号两边只要有以便是NaN,便返回false,且他自身不相等

    console.log("NaN == 1");
    console.log(NaN == 1); //false
    console.log("NaN == NaN");
    console.log(NaN == NaN);//false
    console.log("undefined == NaN");
    console.log(undefined == NaN);//false

    布尔值会转换为数字,false转换为0,true转换为1

    console.log('0 == false');
    console.log(0 == false);
    console.log('1 == true');
    console.log(1 == true);

    对象的转换

var a = [];
      var b = [];
      var c = {};
      var d = {};
      console.log("[] == []");
      console.log(a == b); // false
      console.log("[] == {}");
      console.log(a == c); // false
      console.log("{} == {}");
      console.log(d == c); // false
      console.log("[] == ![]");
      console.log(a == !b); // true
// 对于前三个的原理是一样的,当两个值都是对象 (引用值) 时, 比较的是两个引用值在内存中是否是同一个对象. 因为此 [] 非彼 [], 虽然同为空数组, 确是两个互不相关的空数组, 所以为false。
// 而最后一个是因为右边空数组会转化为true,取反变为false,false变为0;左边空数组变为空字符串再变为0,0==0就为true。

  3、· 点号操作符

    数字、字符串等直接做·操作调用方法时,隐式地将类型转换成对象。

  4、if()语句

    括号里的表达式部分会被隐式转换为布尔类型进行判别

10.出现小数精度丢失的原因, JavaScript可以存储的最大数字、最大安全数字, JavaScript处理大数字的方法、避免精度丢失的方法

答:精度丢失:

  因为有些小数在计算机使用二进制方式表示时无法准确的表示出来,类似于十进制中的1/3一样,无理数,无限循环.

  可是计算机存储小数的类型不管是float还是double都是有位数限制的,所以他们存储的只是一个近似值,这就导致了精度丢失.

  解决办法:

  对于整数,前端出现问题的几率可能比较低,毕竟很少有业务需要需要用到超大整数,只要运算结果不超过 Math.pow(2, 53) 就不会丢失精度。

  对于小数,前端出现问题的几率还是很多的,尤其在一些电商网站涉及到金额等数据。解决方式:把小数放到位整数(乘倍数),再缩小回原来倍数(除倍数)

// 0.1 + 0.2
(0.1*10 + 0.2*10) / 10 == 0.3 // true

一位网友写的方法,对小数的加减乘除丢失精度做了屏蔽,换算后的整数不能超过 9007199254740992:

(大整数的精度丢失和浮点数本质上是一样的,尾数位最大是 52 位,因此 JS 中能精准表示的最大整数是 Math.pow(2, 53),十进制即 9007199254740992。)

/**
 * floatObj 包含加减乘除四个方法,能确保浮点数运算不丢失精度
 *
 * 我们知道计算机编程语言里浮点数计算会存在精度丢失问题(或称舍入误差),其根本原因是二进制和实现位数限制有些数无法有限表示
 * 以下是十进制小数对应的二进制表示
 *      0.1 >> 0.0001 1001 1001 1001…(1001无限循环)
 *      0.2 >> 0.0011 0011 0011 0011…(0011无限循环)
 * 计算机里每种数据类型的存储是一个有限宽度,比如 JavaScript 使用 64 位存储数字类型,因此超出的会舍去。舍去的部分就是精度丢失的部分。
 *
 * ** method **
 *  add / subtract / multiply /divide
 *
 * ** explame **
 *  0.1 + 0.2 == 0.30000000000000004 (多了 0.00000000000004)
 *  0.2 + 0.4 == 0.6000000000000001  (多了 0.0000000000001)
 *  19.9 * 100 == 1989.9999999999998 (少了 0.0000000000002)
 *
 * floatObj.add(0.1, 0.2) >> 0.3
 * floatObj.multiply(19.9, 100) >> 1990
 *
 */
var floatObj = function() {
    
    /*
     * 判断obj是否为一个整数
     */
    function isInteger(obj) {
        return Math.floor(obj) === obj
    }
    
    /*
     * 将一个浮点数转成整数,返回整数和倍数。如 3.14 >> 314,倍数是 100
     * @param floatNum {number} 小数
     * @return {object}
     *   {times:100, num: 314}
     */
    function toInteger(floatNum) {
        var ret = {times: 1, num: 0}
        var isNegative = floatNum < 0
        if (isInteger(floatNum)) {
            ret.num = floatNum
            return ret
        }
        var strfi  = floatNum + ''
        var dotPos = strfi.indexOf('.')
        var len    = strfi.substr(dotPos+1).length
        var times  = Math.pow(10, len)
        var intNum = parseInt(Math.abs(floatNum) * times + 0.5, 10)
        ret.times  = times
        if (isNegative) {
            intNum = -intNum
        }
        ret.num = intNum
        return ret
    }
    
    /*
     * 核心方法,实现加减乘除运算,确保不丢失精度
     * 思路:把小数放大为整数(乘),进行算术运算,再缩小为小数(除)
     *
     * @param a {number} 运算数1
     * @param b {number} 运算数2
     * @param digits {number} 精度,保留的小数点数,比如 2, 即保留为两位小数
     * @param op {string} 运算类型,有加减乘除(add/subtract/multiply/divide)
     *
     */
    function operation(a, b, digits, op) {
        var o1 = toInteger(a)
        var o2 = toInteger(b)
        var n1 = o1.num
        var n2 = o2.num
        var t1 = o1.times
        var t2 = o2.times
        var max = t1 > t2 ? t1 : t2
        var result = null
        switch (op) {
            case 'add':
                if (t1 === t2) { // 两个小数位数相同
                    result = n1 + n2
                } else if (t1 > t2) { // o1 小数位 大于 o2
                    result = n1 + n2 * (t1 / t2)
                } else { // o1 小数位 小于 o2
                    result = n1 * (t2 / t1) + n2
                }
                return result / max
            case 'subtract':
                if (t1 === t2) {
                    result = n1 - n2
                } else if (t1 > t2) {
                    result = n1 - n2 * (t1 / t2)
                } else {
                    result = n1 * (t2 / t1) - n2
                }
                return result / max
            case 'multiply':
                result = (n1 * n2) / (t1 * t2)
                return result
            case 'divide':
                result = (n1 / n2) * (t2 / t1)
                return result
        }
    }
    
    // 加减乘除的四个接口
    function add(a, b, digits) {
        return operation(a, b, digits, 'add')
    }
    function subtract(a, b, digits) {
        return operation(a, b, digits, 'subtract')
    }
    function multiply(a, b, digits) {
        return operation(a, b, digits, 'multiply')
    }
    function divide(a, b, digits) {
        return operation(a, b, digits, 'divide')
    }
    
    // exports
    return {
        add: add,
        subtract: subtract,
        multiply: multiply,
        divide: divide
    }
}();

猜你喜欢

转载自www.cnblogs.com/memphis-f/p/11913669.html