美文网首页
javascript基础知识问答——变量和类型

javascript基础知识问答——变量和类型

作者: 妹姐在线 | 来源:发表于2020-06-06 23:47 被阅读0次
    • 1.JavaScript规定了几种语言类型
    • 2.JavaScript对象的底层数据结构是什么
    • 3.Symbol类型在实际开发中的应用、可手动实现一个简单的Symbol
    • 4.JavaScript中的变量在内存中的具体存储形式
    • 5.基本类型对应的内置对象,以及他们之间的装箱拆箱操作
    • 6.理解值类型和引用类型
    • 7.nullundefined的区别
    • 8.至少可以说出三种判断JavaScript数据类型的方式,以及他们的优缺点,如何准确的判断数组类型
    • 9.可能发生隐式类型转换的场景以及转换原则,应如何避免或巧妙应用
    • 10.出现小数精度丢失的原因,JavaScript可以存储的最大数字、最大安全数字,JavaScript处理大数字的方法、避免精度丢失的方法

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

    JavaScript中的每一个值都有它自己的类型,JavaScript规定了七种语言类型:

    1. Undefined:未定义,声明了变量但是没有赋值
    var m;
    console.log(s);   //undefined  ,如果一个没有声明的变量,会报错
    
    1. Null:空
    var m=null;
    console.log(m);          //null
    console.log(typeof(m));  //object
    //咦?为什么是object?这实际上是涉及到JavaScript内部的存储机制,js中的数据在底层是以二进制存储
    //如果前三位为0,那么就会判定为object,而null的所有都为0,自然也就是object类型了,记住就好了,特殊情况特殊处理。
    
    1. Boolean:布尔值(true/false)
    var m=true;
    console.log(typeof(m));    //boolean
    
    1. String :表示字符串类型
    var test0='test';
    console.log(typeof(test0));//返回  string
    var test1=new String('test');
    console.log(typeof(test1));//返回   object
    
    1. Number:表示数字
    var m=1;
    console.log(typeof(m));     //输出 number
    
    1. 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
    
    1. 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]作为对象存在于堆中
    
    image.png
    栈内存 堆内存
    存储基础数据类型 存储引用数据类型
    按值访问 按引用访问
    存储的值大小固定 存储的值大小不定,可动态调整
    由系统自动分配内存空间 由代码进行指定分配
    空间小,运行效率高 空间大,运行效率相对较低
    先进后出,后进先出 无序存储,可根据引用直接获取

    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();四种常用方法

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

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

    注意:typeof null也是返回object

    1. 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
    
    1. 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

    1. 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

    相关文章

      网友评论

          本文标题:javascript基础知识问答——变量和类型

          本文链接:https://www.haomeiwen.com/subject/ovaxtktx.html