前端面试-JS基础篇
前端面试-JS基础篇1、JS基础类型和复杂类型2、相等运算符 和 严等运算符的区别3、var / let / const 定义的变量有什么区别4、ES6的新特性5、讲述一下ES6中新增的数据结构6、Map数据结构跟普通对象的区别7、箭头函数与普通函数的区别8、JS中null和undefined的区别和判断方法9、JavaScript判断变量类型的方法10、typeof 和 instanceof 的区别11、数组去重的方法12、hasOwnProperty方法13、伪数组和数组的区别14、操作数组的常用方法15、操作对象的常用方法16、如何区分数组和对象17、浅拷贝和深拷贝18、使用JSON.parse(JSON.stringify())实现深拷贝会存在什么问题19、JS原型链20、闭包21、 this的指向普通函数 / 箭头函数22、call / apply / bind的作用和区别23、JS继承的方法和优缺点24、new关键字25、说一说defer和async区别26、cookie、session、sessionStorage、localStorage的区别27、如何删除过期的localstorage数据28、token 能放在cookie中吗29、宏任务和微任务30、setTimeout延迟时间设置为0会在什么时候触发31、JS如何实现多线程32、const和readonly的区别33、原生JS怎么通过JSON坐标文件绘制图形34、JS的作用域和作用域链35、介绍一下JS的事件代理(事件委托)36、字符串转化为数组的方法37、常见的可迭代对象有哪些1、JS基础类型和复杂类型7种基本类型Number、String、Boolean、BigInt长整型、Symbol、Null、Undefined3种引用类型Object、Array、FunctionSymbol是ES6新出的一种数据类型这种数据类型的特点就是没有重复的数据可以作为object的键值BigInt是ES6新出的用于表示任意精度的整数的数据类型2、相等运算符和 严等运算符的区别相等运算符 会发生类型转换 值发生转换后相等返回true严等运算符 不会进行类型转换 比较两个值类型相同且值相等返回true3、var / let / const 定义的变量有什么区别var 用于声明变量作用域内存在变量提升声明前访问会获得undefined, 允许重复声明没有块级作用域只有函数作用域/全局作用域let 用于声明变量, 有变量提升但存在暂时性死区, 在声明前访问会报错不允许重复声明有块级作用域const 用于声明常量, 有变量提升但存在暂时性死区在声明前访问会报错不允许重复声明有块级作用域4、ES6的新特性变量声明let 声明块级变量 const 声明常量箭头函数更简洁的函数定义方式,箭头函数没有自己的this, 不能当做构造函数使用, 只能从外部上下文去获取模块字符串 使用反引号包裹内容解构赋值 从数组或者对象中提取值并赋值给变量类 使用class关键字声明类模块导入和导出 使用export进行导出 , 使用import进行引入Promise 处理异步操作新的数据结构: Set 、Map、WeakMap 和WeakSetSymbol类型Proxy代理 和 Reflect反射Proxy用于拦截和自定义对象的基本操作Reflect 提供了一系列的用于操作对象的方法5、讲述一下ES6中新增的数据结构Map数据结构Map数据结构的键是强引用的可以是任何数据类型Map数据结构按照插入顺序保存键值对Map数据结构查找、插入和删除操作的时间复杂度为 O(1)Map数据结构是可迭代的可以使用for…of和forEach方法进行遍历Map常用操作方法map.set(key, value) 向 Map 中添加一个键值对map.get(key) 根据键获取对应的值map.has(key) 检查 Map 中是否存在指定的键map.delete(key) 删除 Map 中的指定键值对map.clear() 清空 Map 中的所有键值对map.size 获取 Map 中键值对的数量WeakMap数据结构只接受对象作为键数据的键是弱引用的当键对象没有其他引用时条目会被自动回收不可遍历 无法通过for…of遍历 也无法通过keys(), values()和 entries()方法获取键值对没有size属性获取键值对数量WeakMap常用操作方法WeakMap.set(key, value) 向 WeakMap 中添加一个键值对WeakMap.get(key) 根据键获取对应的值WeakMap.has(key) 检查 WeakMap 中是否存在指定的键WeakMap.delete(key) 删除 WeakMap 中的指定键值对WeakMap.clear() 清空 WeakMap 中的所有键值对Set数据结构Set数据结构的值是唯一的不会重复Set数据结构的值是强引用的可以是任何数据类型Set数据结构按照插入顺序保存值查找、插入和删除操作的时间复杂度为 O(1)Set数据结构是可迭代的可以使用for…of和forEach方法进行遍历Set常用操作方法new Set() 创建一个新的 Set 对象set.add(value) 向 Set 中添加一个值set.has(value) 检查 Set 中是否存在指定的值set.delete(value) 删除 Set 中的指定值set.clear() 清空 Set 中的所有值set.size 获取 Set 中值的数量WeakSet数据结构只接受对象作为值数据的值是弱引用的当值对象没有其他引用时条目会被自动回收不可遍历 无法通过for…of遍历 也无法通过keys(), values()和 entries()方法获取键值对没有get方法获取值也没有size属性获取值的数量WeakSet常用操作方法new WeakSet() 创建一个新的 WeakSet 对象WeakSet.set(key, value) 向 WeakSet 中添加一个值WeakSet.has(key) 检查 WeakSet 中是否存在指定的值WeakSet.delete(key) 删除 WeakSet 中的指定值WeakSet.clear() 清空 WeakSet 中的所有值6、Map数据结构跟普通对象的区别普通对象Object普通对象的键只能是字符串或符号Symbol, 使用其他类型会被自动转换为字符串普通对象Object查找、插入和删除操作的时间复杂度为 O(1)但在某些情况下如键非常多时可能会退化为 O(n)普通对象遍历顺序不保证尤其是当键是数字时可能会按照数字顺序而不是插入顺序遍历使用 for…in 遍历时会遍历对象的所有可枚举属性包括原型链上继承的属性普通对象默认包含一些原型链上的属性可能会导致意外行为Map数据结构Map数据结构的键可以是任何类型包括对象、函数、数组等Map查找、插入和删除操作的时间复杂度为 O(1)性能更稳定内部实现优化适合处理大量数据使用for…of 或 Map.prototype.forEach 遍历时会按照插入顺序遍历键值对Map数据结构没有默认的原型链不包含任何默认属性更适合用作数据结构避免原型链污染7、箭头函数与普通函数的区别箭头函数相当于匿名函数简化了函数定义箭头函数简洁语法规则: 如果没有参数 , 直接写一个空括号即可 , 只有一个参数 , 可以省去参数的括号,有多个参数 , 用逗号分割, 如果函数体返回值是单条语句时, 可以省略大括号箭头函数没有自己的this, 只能从函数外部去继承, 所以箭头函数中的this指向在它定义时已经确定了, 之后不会改变箭头函数继承的this指向永远不会改变call()、apply()、bind()等方法也不能改变箭头函数中this的指向由于箭头函数没有自己的this且this指向外层的执行环境且不能改变指向所以不能当做构造函数使用箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。箭头函数没有prototype注意事项arguments 相对于一种 “老式可变参数桶”用来在 function 关键字函数里获取全部实参prototype是函数自带属性是函数独有的“公共仓库”——把方法/属性挂上去所有实例都能共享是实现 JavaScript 的“原型继承”核心机制8、JS中null和undefined的区别和判断方法undefined语义上表示没有值应用场景声明变量的初始状态值调用函数时没有提供实参函数形参就等于undefined访问对象不存在的某个属性获取到的是undefined函数没有返回值默认返回undefined语义上表示无对象 变量当前不引用任何的对象地址应用场景声明对象类型的值 可以使用null初始化变量undefined和null的区别undefined通过typeof判断的类型是undefined, null通过typeof判断的类型是’object’null undefined输出truenull undefined输出false 严等运算符会比较数据类型undefined和null是两种不同的数据类型9、JavaScript判断变量类型的方法typeof、instanceof、Object.prototype.toString.call()对象原型链判断方法、constructor (用于引用数据类型)Object.prototype.toString 方法接受一个参数并返回一个字符串该字符串描述了参数类型10、typeof 和 instanceof 的区别使用 typeof 判断类型会返回对应的类型typeof 可以判断基本数据类型但无法区分数组和对象使用方式如下typeof 42 → numbertypeof hello world → stringtypeof [] → objecttypeof null → object使用instanceof判断类型会返回true/falseinstanceof 不能判断基本数据类型, 但可以通过检测对象原型链来区分对象、函数、数组、日期、正则这些引用类型使用方式如下42 instanceof Numebr → false[] instanceof Array → true{} instanceof Object → true(() {}) instanceof Function → true注意null 是原型链的终点没有内部原型[[Prototype]]所以任何 instanceof 测试都会返回 false11、数组去重的方法双重循环 外层循环元素内层循环找重复functionunique(arr){varres[];for(vari0;iarr.length;i){varhasfalse;for(varj0;jres.length;j){// 内层扫描已存结果if(res[j]arr[i]){hastrue;break;}}if(!has)res.push(arr[i]);}returnres;}// 测试console.log(unique([3,5,3,2,5,8]));// [3, 5, 2, 8]reduce累计遍历 includesconstuniqueArrayoriginalArray.reduce((acc,current){if(!acc.includes(current)){acc.push(current)}returnacc},[])// 第二个参数 [] 是累加器accumulator的初始值filter过滤遍历 indexOfconstarr[3,5,3,2,5,8];constuniquearr.filter((item,index)arr.indexOf(item)index)// 等价于当前元素第一次出现的位置 当前下标console.log(unique);// [3, 5, 2, 8]new Set() 利用Set数据结构没有重复数据项// 按 id 去重constdata[{id:1,name:Tom},{id:2,name:Jerry},{id:1,name:Tom Clone}];constuniqueArray.from(newMap(data.map(item[item.id,item])).values());console.log(unique);//[{id:1,name:Tom Clone},{id:2,name:Jerry}]12、hasOwnProperty方法hasOwnProperty 是 JavaScript 中一个对象内置的方法用于检查对象自身是否具有指定的属性并且不是通过原型链继承的(hasOwnProperty方法只能检查对象自身的属性不能检查对象的原型链上的属性)使用场景检查属性是否存在当你需要确定一个对象是否包含某个特定的属性时使用 hasOwnProperty 方法可以避免属性名冲突或错误地访问未定义的属性。遍历对象自身的属性在遍历对象属性时使用 hasOwnProperty 可以确保只遍历对象自身的属性忽略从原型链继承的属性使用例子:obj.hasOwnProperty(prop)obj要检查的对象prop要检查的属性名注意hasOwnProperty 方法是对象的一个标准方法继承自 Object.prototype因此大多数对象都有这个方法。但是如果对象的原型链被修改或者对象的原型链上没有 hasOwnProperty 方法那么直接调用这个方法可能会引发错误。为了安全起见可以使用 Object.prototype.hasOwnProperty.call(obj, prop) 的方式调用这样可以确保对象的原型链被修改也能正确调用 hasOwnProperty 方法13、伪数组和数组的区别伪数组的类型是Object伪数组可以使用的length属性查看长度也可以使用[index]索引去获取某个元素但是不能使用数组的其他方法也不能改变长度遍历使用for in方法数组的类型是Array数组可以使用length属性查看长度使用[index]索引去获取某个元素遍历数组使用for of方法和其他操作数组的常用方法Array.from(arr) 可以将类数组对象伪数组或可迭代对象转为真正数组14、操作数组的常用方法在JavaScript中提供了一系列与数组操作相关的方法可以用来操纵数组如添加、删除、查找、排序和遍历元素等。以下是一些常用的数组方法添加元素push() 在数组的末尾添加一个或多个元素并返回新的长度unshift() 在数组的开头添加一个或多个元素并返回新的长度删除元素pop() 删除数组的最后一个元素并返回那个元素shift() 删除数组的第一个元素并返回那个元素splice() 通过删除、替换或添加新元素来改变数组的内容查找元素indexOf() 查找元素在数组中的索引lastIndexOf() 从数组的末尾开始查找元素find() 查找第一个满足条件的元素findIndex() 查找第一个满足条件的元素的索引排序和翻转sort() 对数组元素进行排序reverse() 反转数组中元素的顺序遍历和过滤for of 遍历可迭代对象(包括数组、Set、Map和字符串等forEach() 对数组的每个元素执行一次提供的函数map() 创建一个新数组其结果是该数组中的每个元素都调用一次提供的函数后的返回值filter() 创建一个新数组包含通过所提供函数实现的测试的所有元素reduce() 对数组中的每个元素执行一个由您提供的reducer函数升序执行将其结果汇总为单个返回值reduceRight() 类似reduce()但是从数组的末尾开始累加归并数组concat(): 合并两个或多个数组slice(): 提取原数组的一部分返回一个新数组其它方法join() 将数组或一个类数组对象的所有元素连接成一个字符串并返回这个字符串toString() 将各种类型的值转换为字符串split() 把字符串转换成数组toLocaleString() 返回一个表示数组内容的本地化字符串15、操作对象的常用方法Object.assign 用于合并多个源对象同键覆盖使用方式Object.assign(obj, {name:Tom})delete 运算符 彻底移除对象的键值会触发代理钩子使用方式delete obj.age对象解构使用方式const {age, ...rest} obj 生成age变量和不含 age属性的新对象rest对象遍历方法示例说明for…infor(const k in obj){...}遍历的键含原型链上的可枚举属性需hasOwnProperty过滤Object.keysObject.keys(obj).forEach(k...)只遍历自身可枚举键不遍历原型链上的键Object.entriesObject.entries(obj).forEach(([k,v])...)同时拿到键和值Object.valuesObject.values(obj).forEach(v...)只遍历值对象元编程方法示例说明冻结Object.freeze(obj)让整个对象不可增删改常量对象密封Object.seal(obj)允许改值禁止增删键阻止扩展Object.preventExtensions(obj)只能改现有键不能新增键创建无原型链的对象Object.create(null)生成纯净字典无toString等继承键属性描述符Object.defineProperty(obj,a,{value:1,writable:false})精准控制读写、枚举、配置合并深层structuredClone(obj)ES2021深拷贝对象含循环引用注意Object.create主要用于创建指定原型的新对象传入的第一个参数用于指定原型第二个参数用于定义新建对象的属性和配置属性特性可修改、可删除和可枚举16、如何区分数组和对象Array.isArray()方法可以区分对象和数组Array.isArray(obj)返回true时obj为数组, 返回false时obj为对象instanceof 操作符可以区分对象和数组obj instanceof Array语句返回true时obj为数组返回false时obj为对象通过Object.prototype.toString方法区分数组和对象通过对象和数组的constructor属性可以区分数组和对象 constructor属性可被修改obj.constructor Object为ture时obj为对象obj.constructor Array为ture时obj为数组使用 hasOwnProperty 方法检查是否具有length长度属性数组具有length属性返回true对象没有length属性返回false17、浅拷贝和深拷贝浅拷贝 只复制指向对象的指针不复制对象本身新旧变量指向同一对象地址修改其中一个会影响另一个深拷贝递归复制对象的所有层级的属性新旧变量指向不同对象地址修改互不影响相互独立深拷贝主要通过JSON.parse(JSON.stringify())或手动递归实现18、使用JSON.parse(JSON.stringify())实现深拷贝会存在什么问题JSON.stringify 和 JSON.parse 是用于序列化和反序列化 JSON 数据的方法也可以用来实现对象深拷贝无法处理函数属性无法处理循环引用无法处理特殊类型如Date、Map、Set等对象类型以及Symbol类型JSON.stringify和JSON.parse会导致对象丢失原型链性能问题 JSON.stringify和JSON.parse涉及字符串序列化和反序列化处理大型数据时导致性能问题数据精度丢失和类型错误 NaNInfinity,-Infinity等特殊数据会被转换成null BigInt类型数据会被抛出TypeError错误19、JS原型链prototype所有的函数都有原型prototype属性这个属性指向函数的原型对象proto这是每个对象(除null外)都会有的属性叫做__proto__这个属性会指向该对象的原型constructor: 每个prototype原型都有一个constructor属性指向该关联的构造函数原型链获取对象时如果这个对象上本身没有这个属性时它就会去它的原型__proto__上去找如果还找不到就去原型的原型上去找直到找到原型链最顶层Object.prototype为止Object.prototype对象也有__proto__属性值为nullnull 是原型链的终点没有内部原型[[Prototype]]Object.create(null) 可以创建一个没有原型链的纯净对象20、闭包闭包 是指一个函数内部定义了另一个函数并内部函数使用了外部函数的参数或变量函数执行时把内部函数返回到外部环境中就会形成闭包闭包的影响内存泄漏 由于闭包会持续访问外部函数的变量这可能导致内存泄漏。因此当不再需要闭包时应该适当地释放闭包所占用的资源。性能问题 闭包可能会对性能产生影响因为它们需要维护额外的作用域链。在性能敏感的应用中应谨慎使用闭包。闭包的应用场景数据隐藏、单例模式中封装私有变量、创建函数工厂、防抖和节流函数等21、 this的指向普通函数 / 箭头函数this存在的场景有三种全局执行上下文和函数执行上下文和eval执行上下文eval这种不讨论在全局执行环境中无论是否在严格模式下在任何函数体外部this都指向全局对象当在普通函数调用时如果处于非严格模式之中将会是指向window严格模式指向undefined在对象之中调用将会是指向当前的对象。通过new创建出来的对象将会是指向当前的新对象 如果是使用call、bind、apply修改了指向将会指向绑定后的this在箭头函数之中将会指向函数的外层执行上下文当函数定义之后将会确定当前的this22、call / apply / bind的作用和区别call、apply、bind的作用都是改变函数运行时的this指向fn.call (newThis,params)在改变this指向时会立即执行fn函数, call函数的第一个参数是this的新指向后面依次传入函数fn要用到的参数fn.apply (newThis,paramsArr)在改变this指向时立即执行fn函数, apply函数的第一个参数是this的新指向,第二个参数是fn要用到的参数数组fn.bind (newThis,params)在改变this指向的时候返回一个已经改变执行上下文的fn函数不会立即执行函数。bind函数的第一个参数是this的新指向后面的参数可以直接传递也可以按数组的形式传入。 bind方法只能改变一次fn函数的指向后续再用bind更改无效23、JS继承的方法和优缺点原型链继承核心原理让子类原型指向父类的实例优点写法简单、容易理解缺点①引用类型的值会被所有实例共享②在子类实例对象创建时不能向父类传参构造函数继承核心原理在子类中调用父类构造函数优点①避免了引用类型的值会被所有实例共享②在子类实例对象创建时可以向父类传参缺点无法继承父类原型上的方法组合继承核心原理原型链 构造函数组合优点既可继承属性和方法也可传参和避免共享问题缺点父类构造函数会被调用两次一是创建子类原型对象时二是子类构造函数内部Class 继承语法糖 本质是原型继承优点: 语法清晰易读 支持super关键字缺点: 本质是原型继承存在相同的原型链限制24、new关键字new 操作符后面必须跟一个构造器这个构造器可以是一个类class也可以是一个普通函数。如果后面跟的是普通函数该函数会作为构造函数执行如果后面跟的是原始字面量如 1、“abc”、true会触发语法错误SyntaxError因为字面量不是合法的构造器。new 关键字会进行如下操作创建了一个新对象this指向了这个对象构造函数的属性和方法都赋给了这个对象把新对象返回到全局25、说一说defer和async区别一般情况下HTML文件都是按顺序执行的浏览器在解析文档时遇到script标签就会停止解析HTML先加载JS文件加载完后立即执行执行完毕后才能继续解析文档这样会阻塞文档解析在script标签中写入defer属性时就会使JS文件异步加载即HTML执行遇到script标签时JS加载和文档解析同时进行不阻塞文档解析在JS加载和文档解析完成后再执行JS脚本在script标签中写入async属性时在HTML执行遇到script标签时JS加载和文档解析同时进行不阻塞文档解析并且在JS加载完成后立即执行JS脚本26、cookie、session、sessionStorage、localStorage的区别方案存储位置生命周期容量优点缺点典型场景Cookie客户端浏览器由服务器控制会话或持久级4 KB同域跨页共享安全策略丰富体积过小每次请求自动携带增加流量与延迟用户令牌token、会话跟踪、个性化设置Session服务器端由服务器控制会话或持久级取决于数据库同域跨页共享数据不落地浏览器防 XSS 泄露增加服务器内存/DB 负担需解决分布式会话一致性问题用户会话管理、登录状态、购物车SessionStorage客户端会话级标签页或 iframe 关闭即清5-10 MB大容量API 简洁不受请求携带影响仅限当前标签页/iframe无法跨窗口共享表单过滤信息、分页状态、页面临时数据LocalStorage客户端持久级手动清理5-10 MB大容量持久化同域跨页共享永不过期可能累积垃圾XSS 可被读取不随请求发送主题皮肤、离线缓存、JWT 刷新令牌、用户偏好27、如何删除过期的localstorage数据惰性删除惰性删除是指获取数据的时候拿到存储的时间和当前时间做对比如果超过过期时间就清除Cookie。定时删除每隔一段时间执行一次删除操作并通过限制删除操作执行的次数和频率来减少删除操作对CPU的长期占用。 LocalStorage清空应用场景token存储在LocalStorage中要清空28、token 能放在cookie中吗token一般是用来判断用户是否登录的它内部包含的信息有uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign签名token 的前几位以哈希算法压缩成的一定长度的十六进制字符串token一般存储在sessionStorage/localStorage里面不建议存放到Cookie中容易产生CSRF问题。token的出现就是为了解决用户登录后的鉴权问题如果采用cookiesession的鉴权方式则无法有效地防止CSRF攻击。同时如果服务端采用负载均衡策略进行分布式架构session也会存在一致性问题需要额外的开销维护session一致性token可以存放在Cookie中由后端来判断token是否过期无需前端来判断所以token存储在cookie中无需设置cookie的过期时间如果token失效就让后端在接口中返回固定的状态表示token失效需要重新登录然后重新设置cookie中的tokentoken认证流程客户端使用用户名跟密码请求登录服务端收到请求去验证用户名与密码验证成功后服务端签发一个 token 并把它发送给客户端客户端接收 token 以后会把它存储起来比如放在 cookie 里或者 localStorage 里客户端每次发送请求时都需要带着服务端签发的 token把 token 放到 HTTP 的 Header 里服务端收到请求后需要验证请求里带有的 token 如验证成功则返回对应的数据29、宏任务和微任务宏任务Macrotask宏任务是指在当前执行栈清空后由事件循环触发依次处理的宏任务队列的任务。常见宏任务包括Promise 的状态改变如从 pending 到 fulfilled 或 rejected属于宏任务setTimeoutsetInterval用户交互事件如点击、键盘输入等微任务微任务是指在当前执行栈清空后在下一个宏任务之前优先执行的微任务队列的任务。常见微任务包括Promise.then/catch/finally方法注册的回调函数MutationObserverDOM 变化观察器在当前宏任务同步代码执行完毕后会一次性执行所有收集到变化的回调函数30、setTimeout延迟时间设置为0会在什么时候触发在JS文件执行时首先会形成一个全局任务主任务进入任务队列中等待执行。在全局任务执行时遇到延迟时间为0的定时器会把定时器交给计时线程处理因为延迟时间为0计时线程会立刻执行定时器把定时器内部的回调函数包装成任务放进任务队列等待执行当执行栈为空并且没有优先级更高的其他任务时就会把回调函数押入执行栈执行31、JS如何实现多线程JavaScript本身是单线程的但是可以通过实现多线程来提高性能和用户体验。多线程允许JavaScript在等待用户交互或网络请求时执行其他任务从而提高页面加载速度和响应速度。JavaScript实现多线程的方式JavaScript有多种实现多线程的方式包括Web Workers、SharedArrayBuffer、WebAssembly等。其中Web Workers允许在后台线程中运行JavaScript代码而SharedArrayBuffer和BufferSource API则允许在多个线程之间共享数据。使用Web Workers实现多线程的方式使用Web Workers实现多线程需要创建一个新的worker线程并将需要执行的代码作为字符串传递给worker。worker线程可以访问全局对象messageChannel的postMessage方法来发送消息主线程可以使用onmessage方法来接收消息并执行相应的操作如何保证多线程安全多线程存在的安全问题主要包括数据竞争和死锁等。为了解决这些问题需要使用同步机制如使用Promise、async/await等异步编程方式或者使用事件循环、共享内存等机制来保证数据的一致性和安全性。多线程应用场景在实际应用中多线程可以用于提高页面加载速度和响应速度例如在电商网站中可以使用Web Workers在后台线程中加载和处理商品图片从而提高页面加载速度和用户体验。同时多个并发请求也可以使用Web Workers并行处理提高系统性能和响应速度32、const和readonly的区别相同点const和readonly都可以用于限制变量修改不同点const是编译期常量定义时必须初始化且值(地址)不可变引用类型的const常量可以修改内部的属性任何修改都会导致编译错误主要用于修饰常量数据和类的静态字段等readonly是运行期只读在运行时确定值赋值后就不能再修改主要用于修饰类的实例字段和静态字段引用类型的readonly字段只能保证其地址不会发生改变但可以修改对象内部属性33、原生JS怎么通过JSON坐标文件绘制图形使用HTML5的Canvas API 绘制图形的具体流程如下1、准备画布在HTML中创建一个canvas元素2、使用fetch API异步加载JSON文件3、解析绘制 在获取到数据后 使用Canvas 2D上下文getContext(‘2d’)的绘图方法4、为加载和解析过程添加错误处理34、JS的作用域和作用域链作用域可以分为全局作用域浏览器/node环境、函数作用域和块级作用域全局作用域在所有函数、代码块之外定义的变量/函数整个程序都能访问函数作用域在函数内部定义的变量/函数仅该函数内部可以访问由{}如if、for、while和let、const声明创建的作用域变量仅在块内有效作用域链访问变量时JS会先在当前作用域查找找不到就会向上级作用域查找直到全局作用域都找不到则报错35、介绍一下JS的事件代理(事件委托)事件代理是利用 JS 事件冒泡机制把子元素事件监听器统一绑定到父元素由父元素代理内部所有子元素的事件监听。减少 DOM 操作、提升性能、支持动态元素优点减少子元素监听器的内存占用和通过JS代码对DOM的操作支持动态新增、删除子元素无需重新绑定子元素的事件监听器集中管理易于维护ul idlistli data-id1苹果/lili data-id2橙子/lili data-id3香蕉/li/ulscript// 只在父元素绑定一次document.getElementById(list).addEventListener(click,function(e){// 通过>constide.target.dataset.idif(id)console.log(选中水果,id)})/script36、字符串转化为数组的方法split(separator, limit)split方法可以按指定分隔符分割字符串返回数组separator 必选参数可传入字符串或正则表达式如 ‘ ’可将字符串的每个字符拆分limit 可选参数 限制返回数组的最大长度示例:hello.split( ) [h, e, l, l, o]Array.from(str, mapFn, thisArg)将类数组对象或可迭代对象转为真正数组str 必选参数可传入可迭代对象或类数组对象mapFn 可选参数 类似于Array.prototype.map对每个元素进行处理后再放入新数组thisArg 可选参数第二个参数映射函数mapFn的this指向仅在mapFn为非箭头函数时有效示例:Array.from(test) [t, e, s, t]扩展运算符[...str]: 可以简洁地将字符串拆分为单个字符的数组对Unicode字符兼容性好示例: […‘abc’] [‘a’, ‘b’, ‘c’]37、常见的可迭代对象有哪些常见的可迭代对象主要包括原生内置对象和自定义实现了迭代器接口的对象部署了Symbol.iterator方法的对象常见的可迭代对象如下:原生字符串集合对象Set/MapSet数据迭代其值Map迭代其键值对类数组对象 如NodeList(DOM节点集合)、HTMLCollection(存储页面中一组特定的HTML元素)、有length属性的普通对象数组Array生成器对象由function*生成的对象自动实现迭代器接口生成器示例如下function*generateId(){letid1while(true){// 理论上无限循环但在生成器函数中可以暂停循环执行直到下一次调用yieldid;//暂停并返回当前ID}}constidGeneratorgenerateId()//生成器对象console.log(idGenerator.next().value)// 1console.log(idGenerator.next().value)// 2