• 变量和类型


    前端工程师自检清单

     

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

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

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

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

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

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

    7. null和 undefined的区别

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

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

    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
        }
    }();
  • 相关阅读:
    【代码笔记】iOS-判断textField里面是否有空
    【代码笔记】iOS-判断字符串是否为空
    【代码笔记】iOS-判断中英文混合的字符长度的两种方法
    【代码笔记】iOS-判断有无网络
    【代码笔记】iOS-判断是否是iPhone5
    iOS动画-扩散波纹效果
    (转)对称加密与非对称加密,以及RSA的原理
    (转)iOS GPUImage研究总结
    @inerface的11条规范写法
    Python开发之路
  • 原文地址:https://www.cnblogs.com/memphis-f/p/11913669.html
Copyright © 2020-2023  润新知