JS中的数据类型

原始值类型「基本数据类型 & 值类型」

  • number 整数、小数、零、负数、NaN(不是一个有效数字,但是属于数字类型)、Infinity(无穷大的值)…
  • string 字符串:“”、‘’、``(ES6中的模板字符串,更方便的实现字符串拼接)
  • boolean 布尔:true/false
  • null 空
  • undefined 未定义
  • symbol 唯一值(ES6+)
  • bigint 大数(ES6+)
    Symbol数据类型作用
  1. 想创建一个和别人不相等的值(唯一值)
  • Symbol不能被 new 执行
  • 每一次 “Symbol(描述)” 这样执行,都是创建一个新的唯一值出来
    • console.log(Symbol() === Symbol()) //false
  1. 可以给对象设置一个symbol类型(唯一)的成员「属性名」
  • 对象的“成员值”,可以是任意类型的值
  • 但是对象的“成员”只能是:字符串、symbol类型「ES6新增的Map结构,允许对象的成员是“对象类型”」 ```javascript let obj = { name: ‘哈哈哈’, age: 25, 0: 100, //-> ‘0’: 100 会把成员默认转换为字符串类型 true: 200 //-> ‘true’: 200 } console.log(obj.name, obj[‘name’]) //‘哈哈哈’ ‘哈哈哈’ console.log(obj[0], obj[‘0’]) //100 100 在进行成员访问的时候,如果访问的成员,不是字符串格式,也要先转换为字符串格式

    console.log(obj.true, obj[‘true’], obj[true]) //200 200 200

    let aa = { x: 10 } let bb = { y: 20 } let obj = { // aa: ‘哈哈哈’ //->成员名叫做 “aa” [aa]: ‘哈哈哈’ //->把aa变量的值,作为对象的一个成员 => ‘[object Object]’:’哈哈哈’ } obj[bb] = ‘呵呵呵’ // => ‘[object Object]’:’呵呵呵’ console.log(obj)

    console.log(obj[aa] === obj[bb]) //true

    let sym = Symbol(‘AA’) let obj = { name: ‘哈哈哈’, age: 25, [Symbol(‘AA’)]: 200, //此成员是 Symbol 类型,不是字符串类型

} console.log(obj) console.log(obj[Symbol(‘AA’)]) //undefined 此处又创建了一个新的唯一值,和之前的唯一成员不是一个值 console.log(objsym) //300

  1. 1. 对象中“成员”的特点:
  2. - 类型的限制:字符串、symbol
  3. - 规则的限制:是否可删除、是否可修改、是否可枚举
  4. > 可枚举:可以被 for/in循环 或者 Object.keys 列举的成员,被称为可枚举的「一般来讲,自己设置的成员都是可枚举的,内置的成员都是不可枚举的,当然我们可以去设置它的规则」
  5. ```javascript
  6. let obj = {
  7. name: '哈哈哈',
  8. 0: 100,
  9. [Symbol()]: 200
  10. }
  11. let keys = Object.keys(obj) // 获取对象中 “可枚举”、“非symbol类型” 私有的成员
  12. console.log(keys) //['0', 'name']
  13. let keys = Object.getOwnPropertyNames(obj) // 获取对象中 “非symbol类型” 私有的成员
  14. console.log(keys) //['0', 'name']
  15. let keys = Object.getOwnPropertySymbols(obj) // 获取对象中 “symbol类型” 私有的成员
  16. console.log(keys) //[Symbol()]
  17. //需求:获取对象中所有的私有成员
  18. let keys = Object.getOwnPropertyNames(obj)
  19. keys = keys.concat(Object.getOwnPropertySymbols(obj))
  20. console.log(keys) //['0', 'name', Symbol()]
  21. //ES6提供的Reflect也可以获取
  22. let keys = Reflect.ownKeys(obj)
  23. console.log(keys) //['0', 'name', Symbol()]

Object.defineProperty(对象,成员,配置项)

  • 作用1:给对象的某个成员设置(修改)规则「或者 给对象新增一个成员,并设置其规则」 ```javascript let obj = { name: ‘哈哈哈’, 0: 100,

} Object.defineProperty(obj, ‘name’, { configurable: false, //设置为不可删除 writable: false, //设置为不可修改 enumerable: false, //设置为不可枚举 }) Object.defineProperty(obj, ‘age’, { // 如果基于这种方式,”给对象新增成员”,在不设置其规则的情况下,默认规则都是false,也就是默认:不可删除、不可修改、不可枚举 value: 25 }) console.log(Object.getOwnPropertyDescriptor(obj, ‘age’)) //查看对象中某个成员的规则 console.log(Object.getOwnPropertyDescriptors(obj)) //查看对象所有成员的规则

  1. - 作用2:给对象中的某个成员设置劫持函数,在获取和设置成员值的时候,不会去操作堆内存,而是走劫持函数处理
  2. ```javascript
  3. Object.defineProperty(obj,key,{
  4. get(){},
  5. set(){}
  6. })
  7. let obj = {
  8. name: '哈哈哈'
  9. }
  10. Object.defineProperty(obj, 'name', {
  11. configurable: true,
  12. // writable: true, //设置劫持函数后,writable和value这两个规则就不允许设置了
  13. enumerable: true,
  14. get() {
  15. // 访问对象的这个成员时,就会触发get这个函数,函数的返回值,就是获取的成员值
  16. console.log('GET劫持函数')
  17. return '呵呵呵'
  18. },
  19. set(val) {
  20. // 当修改对象的这个成员值的时候,先触发set这个函数,val就是要修改的值
  21. console.log('SET劫持函数', val)
  22. }
  23. })
  1. //给对象新增一个不可枚举的成员(可删除可修改)
  2. const define=function define(obj,key,value){
  3. Object.defineProperty(obj,key,{
  4. value,//"value":value | value:value
  5. writable:true,
  6. configurable:true,
  7. enumerable:false
  8. })
  9. }
  1. for/in循环的“设计缺陷”:
    1. 在迭代的时候,不仅去遍历私有成员,而且还会基于其原型链,遍历其原型对象上的“公有”成员「性能很差」
    2. 只能迭代到 “非symbol类型”、“可枚举” 的成员「限制多、功能弱」

      所以,以后开发的时候,尽可能的不要用for/in循环 :::info 问题:以后对象该如何迭代?

  • 先获取对象所有的私有成员「不受类型和枚举性的限制」 —> 包含所有私有成员的数组
  • 迭代数组中的每个成员,再获取相应的成员值即可 :::
  1. for (let key in obj) {
  2. if (!obj.hasOwnProperty(key)) break //只要找到一个公有的,说明私有的都迭代完毕了,结束循环即可「目的:排除迭代公有的」
  3. console.log(key)
  4. } //因此依旧不建议用for/in
  5. let keys = Reflect.ownKeys(obj) //['0', 'name', 'age', Symbol()]
  6. keys.forEach(key => {
  7. console.log(key, obj[key])
  8. })
  • 回调函数:把创建的函数callback作为“实参”值,传递给另外一个函数fn,在fn执行的过程中,根据相应的需求,把传递进来的callback执行!

    1. function fn(a, b, callback) {
    2. // ...
    3. let total = a + b //30
    4. let res = callback.call(xxx, total)
    5. }
    6. fn(10,20,function (x) {
    7. console.log(x)
    8. return true
    9. }
    10. )

    重写forEach
    1. Array.prototype.myForEach = function myForEach(callback) {
    2. // this->arr 需要迭代的数组
    3. // callback-> 传递的回调函数
    4. let self = this
    5. for (let i = 0; i < self.length; i++) {
    6. callback(self[i], i)
    7. }
    8. }//简易版
    9. Array.prototype.myForEach = function myForEach(callback, context) {
    10. if (typeof callback !== 'function') throw new TypeError('callback必须是一个函数')
    11. let len = +this.length,
    12. k = 0
    13. if (isNaN(len)) throw new TypeError('迭代的数据需要是一个数组/伪数组')
    14. while (k < len) {
    15. if (this.hasOwnProperty(k)) {
    16. // 规避稀疏数组
    17. let item = this[k],
    18. index = k
    19. callback.call(context, item, index)
    20. }
    21. k++
    22. }
    23. }
    24. let arr = [10, 20, 30, 40]
    25. let obj = { name: '哇咔咔' }
    26. arr.myForEach(function (item, index) {
    27. console.log(item, index, this)
    28. }, obj)

    创建数组: ```javascript

  1. 字面量方式 let arr = [10, 20, 30, 40]
  2. 构造函数方式 new Array() -> [] new Array(数字N) -> 创建一个长度是N,但是没有具体项的“稀疏数组” new Array(数字N).fill(null) -> 对每一项进行填充,把其变为“密集数组” new Array(数字N,数字M,字符串S) new Array(字符串S) -> 创建一个数组,括号中的实参都是数组每一项的值
  3. Array.of(参数,…) 「ECMAScript2015」 创建一个数组,传递的参数都是数组中每一项的值 ```

  4. 处理JS的一些底层机制

  • Symbol.asyncIterator
  • Symbol.iterator
  • Symbol.hasInstance
  • Symbol.toPrimitive
  • Symbol.toStringTag
  • BigInt 大数
  • 在JS中存在最大/最小的“安全”数字

    • Number.MAX_SAFE_INTEGER -> 9007199254740991
    • Number.MIN_SAFE_INTEGER -> -9007199254740991
    • 如果使用number类型,超过安全数字后,不论是展示还是计算,都可能会不准确
  • 作用:客户端经常和服务器进行通信,服务器端存储的数字分为 init整型、longInt长整型…,
    • 此时如果服务器返回一个超大数字,客户端即便获取到,一但超过最大的安全数字限制,也会丢失精准度!而BigInt就是为了解决大数的问题!!
  • 如何创建BigInt类型的值
    • 数字后面加一个 n 即可 -> 10n
    • BigInt(数字)
    • 和Symbol一样,不能被 new 执行
  • 场景方案:

    • 服务器端如果需要返回一个超大数字,此时让其以“字符串形式”把数字返回!!
    • 客户端把获取的字符串变为bigint格式 ==> BigInt(字符串)
    • 再按照需要求呈现或者和其它的bigint值进行运算
    • 最后我们把计算好的bigint值,转换为字符串,再传递给服务即可!

      对象类型「引用数据类型」

  • 标准普通对象(纯粹的对象) 例如:{x:10,y:20}

    • proto直接指向Object.prototype
    • 基于Object.create(null)创建的对象,其没有原型链,这样的也是标准普通对象

Object.create.png

  • 标准特殊对象
    • new Array 数组
    • new RegExp 正则
    • new Date 日期对象
    • new Error 错误对象
    • Set/Map 「ES6+新增的数据结构 」
  • 非标准特殊对象
    • 原始值对应的对象数据类型值,都具备[[PrimitiveValue]]属性 带[[]]的属性我们访问不到
      • 例如:new Number(1) 、new String(‘…’)、new Boolean(true)
      • 非标准特殊对象.png
      • 例如:Object(Symbol())、Object(10n)
      • 创建Symbol.png
      • null/undefined比较特殊,没有相应的对象类型值(没有Null/Undefined这样的构造函数)
  • 函数对象function

    数据类型检测

    typeof :检测数据类型的运算符

  • 特点1:返回的结果是字符串,字符串中包含了对应的数据类型

    • typeof typeof typeof [1,2,3]->”string”
  • 特点2:typeof null->“object”
    • typeof不能检测null
    • 检测其他六种原始值类型是没有问题的
  • 特点3:typeof 对象->“object”&&“function“

    • 无法对“对象”进行细分检测(除函数对象外)
      1. //检测是否为函数
      2. const isFunction=function isFunction(obj){
      3. return typeof obj==='function'
      4. }
      1. //笼统校验是否为对象
      2. const isObject=function isObject(obj){
      3. return obj !== null && /^(object|function)$/.test(typeof obj)
      4. }
  • 特点4:typeof未被声明的变量->“undefined”(不会报错)

    typeof检测的底层处理机制

  • 在计算机编程语言中(包括js),所有的数据类型值,在计算机底层都是按照“二进制(0\1)”进行存储的

    • 操作系统是32位的。则存储的二进制值也是32位
    • 操作系统是64位的,则存储的二进制值也是64位(主流)
    • 不同数据类型的二进制值是有特点的
      • 对象:前三位是000
      • 数字:整数是以1开始、浮点数(小数)是以010开始
      • 字符串:以100开始
      • 布尔:以110开始
      • null:64位都是0
      • undefined比较特殊,他存储的是-2^30
  • typeof 就是按照计算机底层存储的二进制值来进行检测的(优势:效率高、检测快)
    • 检测的时候,主要以开始的数字来判断,例如:以000开始的认为对象…
      因为null存储的都是0,前三位肯定也是0,所以被误识别为对象
      typeof null->”object”
    • 如果被检测出来是对象,接下来会再看:当前对象是否实现了 [[Call]]
      如果没有,则返回”object”
      如果实现了,则返回”function”
  • 但是在所有的检测之前,先看”要被检测的值”是否声明了,如果没有声明,直接返回”undefined”

typeof在实战中的运用

  • 检测除null以外的原始值类型(性能高)
  • 笼统的校验是否为对象
  • 检测是否为函数 => isFunction—>if(typeof obj===”function”){…}
  • 处理浏览器兼容(ES6+语法规范,都不兼容IE)

    • 所谓的兼容,无外乎就是:高版本浏览器有这个东西,而低版本浏览器没有这个东西(直接访问会报错xxx is not defined)
    • if(typeof Symbol !== ‘undefined’){
      //浏览器兼容Symbol(浏览器支持ES6+的语法)
      }
    • 以此原理可封装 获取对象所有私有成员(兼容IE、不受枚举和类型限制)方法 如下:
      1. const ownKeys = function ownKeys(obj) {
      2. // 确保传递的是对象
      3. if (!isObject(obj)) throw new TypeError('传递的obj不是一个对象')
      4. // 验证是否支持Reflect
      5. if (typeof Reflect !== 'undefined') return Reflect.ownKeys(obj)
      6. return Object.getOwnPropertyNames(obj)
      7. }
      8. //或以下方法 实际效果一样
      9. const ownKeys = function ownKeys(obj) {
      10. if (!isObject(obj)) throw new TypeError('传递的obj不是一个对象')
      11. let keys = Object.getOwnPropertyNames(obj)
      12. if (typeof Symbol !== 'undefined') {
      13. keys = keys.concat(Object.getOwnPropertySymbols(obj))
      14. }
      15. return keys
      16. }

      数据类型转换

      把其他数据类型转换为Number

      Number([val])
  • 一般用于浏览器的“隐式转换”中,如:

    • 数学运算
    • isNaN检测
    • ==比较
  • 规则:

    • 字符串转换为数字:空字符串变为0,如果出现任何非有效数字字符,结果都是NaN
    • 把布尔转换为数字:true->1 false->0
    • null->0 undefined->NaN
    • Symbol无法转换为数字,会报错:Cannot convert a Symbol value to a number
    • BigInt去除“n”(超过安全数字的会丢失精准度;再长的数字,会按照科学计数法处理)
    • 把对象转换为数字:
      • 先调用对象的 Symbol.toPrimitive 这个方法,如果不存在这个方法
      • 再调用对象的 valueOf 获取原始值,如果获取的值不是原始值
      • 再调用对象的 toString 把其变为字符串
      • 最后再把字符串基于Number方法转换为数字
        1. let obj = { x: 10 }
        2. // obj[Symbol.toPrimitive] --> undefined 不具备这个方法
        3. // obj['valueOf']() --> {x: 10} 结果不是原始值
        4. // obj['toString']() --> '[object Object]' Object.prototype.toString
        5. // Number('[object Object]') --> NaN
        6. console.log(Number(obj))
        1. let arr = [10]
        2. // arr[Symbol.toPrimitive] --> undefined 不具备这个方法
        3. // arr['valueOf']() --> [10] 结果不是原始值
        4. // arr['toString']() --> '10' Array.prototype.toString
        5. // Number('10') --> 10
        6. console.log(Number(arr))
        1. let time = new Date()
        2. // time[Symbol.toPrimitive] --> 在Date.prototype上有Symbol.toPrimitive这个函数
        3. // 具备这个函数,则把这个函数直接执行「可以传递的值:'number'、'string'、'default'」
        4. // time[Symbol.toPrimitive]('number') --> 1689154535927
        5. //「时间戳:距离1970/01/01 00:00:00之间的毫秒差」
        6. console.log(Number(time))
        1. let num = new Number(10)
        2. // num[Symbol.toPrimitive] --> undefined 不具备这个方法
        3. // num['valueOf']() --> 10
        4. console.log(Number(num))
        parseInt([val]) parseFloat([val])
  • 一般用于手动转换

  • 规则:

    • [val]值必须是一个字符串,如果不是则先转换为字符串;
    • 然后从字符串左侧第一个字符开始找,把找到的有效数字字符最后转换为数字「一个都没找到就是NaN」;
    • 遇到一个非有效数字字符,不论后面是否还有有效数字字符,都不再查找了;
    • parseFloat可以多识别一个小数点;
      parseInt([val],[radix])
  • 从[val]左侧开始进行查找,找到所有符合[radix]进制的内容,然后把其按照[radix]进制转换为10进制!!

  • [radix]是设置的进制,取值有效范围是2~36之间,如果不在有效范围内,结果就是NaN
  • [radix]不写或者设置的为0,默认就是10「特殊情况:如果[val]是以“0x”开始的,则默认值是16」

    把其他数据类型转换为String

    转化规则:
  • 拿字符串包起来

  • 对象转字符串

    • String(对象):按照 先找Symbol.toPrimitive -> 再看valueOf -> 最后toString 来处理
    • 对象.toString():直接转换为字符串
    • 特殊:Object.prototype.toString,是用来检测数据类型的
      出现情况:
  • String([val]) 或者 [val].toString()

  • “+”除数学运算,还可能代表的字符串拼接
    • 有两边,一边是字符串,肯定是字符串拼接
    • 有两边,一边是对象,则可能是字符串拼接,还有可能是数学运算
    • 只出现在左边,例如:+”10” 这种方式就是把其它值转换为数字
  • 把其他数据类型转换为Boolean

    转换规则
  • 除了“0/NaN/空字符串/null/undefined”五个值是false,其余都是true

    出现情况:
  • Boolean([val]) 或者 !/!!

  • 条件判断

    “==”与“===”

    “==”相等,两边数据类型不同,需要先转为相同类型,然后再进行比较
  • 对象==字符串 对象转字符串「Symbol.toPrimitive -> valueOf -> toString」

  • null==undefined -> true null/undefined和其他任何值都不相等
    • null===undefined -> false
  • 对象==对象 比较的是堆内存地址,地址相同则相等
  • NaN!==NaN NaN和任何值(包含本身)都不相等
  • 除了以上情况,只要两边类型不一致,剩下的都是转换为数字,然后再进行比较的

    “===”绝对相等,如果两边类型不同,则直接是false,不会转换数据类型「推荐」

    Object.is([val1],[val2]) 检测两个值是否相等「ES6新增的」
  • 核心用的是“===”

  • 特殊:Object.is(NaN,NaN) => true

    面试题

    第一题

    ```javascript var a =? if (a == 1 && a == 2 && a == 3) { console.log(‘OK’) } // 思路一:利用“==”比较会转换数据类型,如果a是一个对象,每一次和数字比较,都会基于 Number 把a先转为数字,再进行比较,而基于Number把对象转数字,会检测对象的 Symbol.toPrimitive、valueOf、toString,只要我们把这三个中的任意一个方法重写,让其执行的时候,分别返回 1/2/3 即可!! var a = { i: 0 } a[Symbol.toPrimitive] = function () { //a[valueOf] = function () { //a[toString] = function () { // this->a return ++this.i//a.i } if (a == 1 && a == 2 && a == 3) { console.log(‘OK’) } //这种是一个思路 var a = [1, 2, 3] a.toString = a.shift if (a == 1 && a == 2 && a == 3) { console.log(‘OK’) }

//思路二:在全局上下文中,基于 var 声明变量,是给 window 设置一个新的成员;当我们访问 a 的时候,是获取 window.a 的值;既然这样,我们就可以基于 Object.defineProperty 给 window 对象中的 a 这个成员,做数据劫持「get/set」! var i = 0 Object.defineProperty(window, ‘a’, { get() { return ++i } }) if (a == 1 && a == 2 && a == 3) { console.log(‘OK’) }

  1. <a name="uD0Ij"></a>
  2. #### 第二题
  3. ```javascript
  4. let arr = [27.2, 0, '0013', '14px', 123]
  5. arr = arr.map(parseInt)
  6. console.log(arr) //[27,NaN,1,1,27]
  7. 第一轮迭代:parseInt(27.2,0)
  8. parseInt('27.2',10)
  9. + 查找符合10进制的内容 '27'
  10. + 把其按照10进制转换为10进制 27
  11. 第二轮迭代:parseInt(0,1)
  12. + NaN 因为没有1进制
  13. 第三轮迭代:parseInt('0013',2)
  14. + 查找符合2进制的内容 '001'
  15. + 把其按照2进制转换为10进制
  16. 第四轮迭代:parseInt('14px',3)
  17. + 查找符合3进制的内容 '1'
  18. + 把其按照3进制转换为10进制
  19. 第五轮迭代:parseInt(123,4)
  20. parseInt('123',4)
  21. + 查找符合4进制的内容 '123'
  22. + 把其按照4进制转换为10进制
  23. parseInt(0013,2)
  24. // 浏览器认为只要以0开始的“数字”,默认都是8进制的,需要隐式转换为10进制的数字
  25. // 0013 -> 8转10
  26. // 0*8^3+0*8^2+1*8^1+3*8^0 => 11
  27. parseInt(11,2)
  28. // 找到符合2进制的内容 -> '11'
  29. // 在把2进制转为10进制 1*2^1+1*2^0 => 3

第三题

  1. console.log(0.1 + 0.2 === 0.3) //false
  2. 不论是整数还是小数(浮点数),在计算机底层都是按照2进制进行存储的
  3. 拓展:把10进制的数字转换为2进制
  4. 整数:除以2,取其余数,然后用商值继续除以2,直到商值变为0,最后把所有余数倒过来拼接
  5. 浮点数:乘以2,取整数部分,直到取整后,变为0
  6. 整数变为2进制不会出现无限循环的情况,但是浮点数是很容易出现的,而计算机底层存储的2进制值,最多64位,所以对于浮点数来讲,很可能存储值的时候,就已经丢失了精准度
  7. 浮点数的运算,可能会导致最后的结果出现很长的小数点位数(精准度丢失)
  8. 0.1+0.2=0.30000000000000004
  9. let num=16
  10. console.log(num.toString())//“16”
  11. console.log(num.toString(2))//把10进制的数字,转换为指定radix进制的字符串“10000”
  12. 问题:如何解决浮点数计算不精准的问题
  13. 方案1:基于toFixed保留小数点位数(一般两位),自带四舍五入机制
  14. let num=0.1+0.2
  15. console.log(+num.toFixed(2)===0.3)//num.toFixed(2)是字符串
  16. 方案2:让计算的浮点数,乘以统一的系数(系数需要按照最大的来,其目的是把浮点数变为整数),最后再除以系数
  17. let num=0.1*10+0.2*10
  18. console.log(num/10===0.3)//true

F{C]NL15Z}J633XY6)}`{XW_tmb.jpg

小结

  • Object.keys(obj) // 获取对象中 “可枚举”、“非symbol类型” 私有的成员
  • Object.getOwnPropertyNames(obj) // 获取对象中 “非symbol类型” 私有的成员
  • Object.getOwnPropertySymbols(obj) // 获取对象中 “symbol类型” 私有的成员
  • Reflect.ownKeys(obj)//返回一个由目标对象自身的属性键组成的数组
  • Object.defineProperty(对象,成员,配置项)
  • //作用1:给对象的某个成员设置(修改)规则「或者 给对象新增一个成员,并设置其规则」 ```javascript Object.defineProperty(obj, ‘name’, { configurable: false, //设置为不可删除 writable: false, //设置为不可修改 enumerable: false, //设置为不可枚举 }) Object.defineProperty(obj, ‘age’, { // 如果基于这种方式,”给对象新增成员”,在不设置其规则的情况下,默认规则都是false,也就是默认:不可删除、不可修改、不可枚举 value: 25 })

console.log(Object.getOwnPropertyDescriptor(obj, ‘age’)) //查看对象中某个成员的规则 console.log(Object.getOwnPropertyDescriptors(obj)) //查看对象所有成员的规则

  1. - Object.getOwnPropertyDescriptor(obj, 'age')) //查看对象中某个成员的规则
  2. - Object.getOwnPropertyDescriptors(obj)) //查看对象所有成员的规则
  3. 作用2:给对象中的某个成员设置劫持函数,在获取和设置成员值的时候,不会去操作堆内存,而是走劫持函数处理
  4. ```javascript
  5. let obj = {
  6. name: '哈哈哈'
  7. }
  8. Object.defineProperty(obj, 'name', {
  9. configurable: true,
  10. // writable: true, //设置劫持函数后,writable和value这两个规则就不允许设置了
  11. enumerable: true,
  12. get() {
  13. // 访问对象的这个成员时,就会触发get这个函数,函数的返回值,就是获取的成员值
  14. console.log('GET劫持函数')
  15. return '呵呵呵'
  16. },
  17. set(val) {
  18. // 当修改对象的这个成员值的时候,先触发set这个函数,val就是要修改的值
  19. console.log('SET劫持函数', val)
  20. }
  21. })

Symbol.toPrimitive
valueOf