转载链接:https://blog.csdn.net/qq_54753561/article/details/122149197
基本数据类型
ES5的5种:Null,undefined,Boolean,Number,String, ES6新增:Symbol表示独一无二的值 ES10新增:BigInt 表示任意大的整数
一种引用数据类型:(本质上是由一组无序的键值对组成)
引用数据类型: Object。包含Object、Array、 function、Date、RegExp。JavaScript不支持创建任何自定义类型的数据,也就是说JavaScript中所有值的类型都是上面8中之一。
null 和 undefined 的区别?
相同: 在 if 语句中 null 和 undefined 都会转为false两者用相等运算符比较也是相等 首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。 不同: undefined 代表的含义是未定义, 定义了形参,没有传实参,显示undefined 一般变量声明了但还没有定义的时候会返回 undefined 对象属性名不存在时,显示undefined 函数没有写返回值,即没有写return,拿到的是undefined null 代表的含义是空对象。也作为对象原型链的终点 null 主要用于赋值给一些可能会返回对象的变量,作为初始化。
ES10新增:BigInt 表示任意大的整数
代码语言:javascript复制
BigInt
数据类型的目的是比Number
数据类型支持的范围更大的整数值。在对大整数执行数学运算时,以任意精度表示整数的能力尤为重要。使用BigInt
,整数溢出将不再是问题。 此外,可以安全地使用更加准确时间戳,大整数ID等,而无需使用变通方法。BigInt目前是第3阶段提案, 一旦添加到规范中,它就是JS 第二个数字数据类型,也将是 JS 第8种基本数据类型:
- 要创建BigInt,只需在整数的末尾追加n即可。比较:
- console.log(9007199254740995n); // → 9007199254740995n
- console.log(9007199254740995); // → 9007199254740996
- 或者,可以调用BigInt()构造函数
- BigInt("9007199254740995"); // → 9007199254740995n
- // 注意最后一位的数字
- 9007199254740992 === 9007199254740993; // → true
- console.log(9999999999999999); // → 10000000000000000
数据类型存储以及堆栈内存是什么
基本数据类型:直接存储在栈内存中,占据空间小,大小固定,属于被频繁使用的数据。指的是保存在栈内存中的简单数据段;number string 布尔 引用数据类型:同时存储在栈内存与堆内存中,占据空间大,大小不固定。 引用数据:类型将指针存在栈中,将值存在堆中。当我们把对象值赋值给另外一个变量时,复制的是对象的指针,指向同一块内存地址,意思是,变量中保存的实际上只是一个指针,这个指针指向内存堆中实际的值,数组 对象
堆(heap)和栈(stack)有什么区别存储机制
栈: 是一种连续储存的数据结构,具有先进后出后进先出的性质。 通常的操作有入栈(压栈),出栈和栈顶元素。想要读取栈中的某个元素,就是将其之间的所有元素出栈才能完成。 堆: 是一种非连续的树形储存数据结构,具有队列优先,先进先出; 每个节点有一个值,整棵树是经过排序的。特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。常用来实现优先队列,存取随意。
js数据类型判断,条件分支
if语句和逻辑运算
所有基本类型中Boolean值是false的只有6个,分别是 : 0 NaN ' ' null undefined false 引用类型Boolean值全是true. if条件是单个值时,如果是truly值,条件成立, 如果是falsely值,条件不成立
逻辑运算符以及他们的运算规则?
代码语言:javascript复制
- && 逻辑与 两边都是true,才返回true,否则返回false
- || 逻辑或 两边只要有一个是true,就返回true,否则返回false
- !逻辑非 用来取一个布尔值相反的值
数据类型判断
代码语言:javascript复制
- typeof 对于基本数据类型判断是没有问题的,但是遇到引用数据类型(如:Array)是不起作用
- console.log(typeof 2); // number
- console.log(typeof null); // object
- `instanceof` 只能正确判断引用数据类型 而不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型
- console.log([] instanceof Array); // true
- console.log(function(){} instanceof Function); // true
- console.log({} instanceof Object); // true
- constructor 似乎完全可以应对基本数据类型和引用数据类型 但如果声明了一个构造函数,并且把他的原型指向了 Array 的原型,所以这种情况下,constructor 也显得力不从心
- console.log((true).constructor === Boolean); // true
- console.log(('str').constructor === String); // true
- console.log(([]).constructor === Array); // true
- console.log((function() {}).constructor === Function); // true
- console.log(({}).constructor === Object); // true
- console.log((2).constructor === Number); // true
- Object.prototype.toString.call() 完美的解决方案,可以通过toString() 来获取每个对象的类型,
- `Object.prototype.toString.call()` 使用 Object 对象的原型方法 toString 来判断数据类型:
- var a = Object.prototype.toString;
- console.log(a.call(2));
- console.log(a.call(true));
- console.log(a.call('str'));
- console.log(a.call([]));
- console.log(a.call(function(){}));
- console.log(a.call({}));
- console.log(a.call(undefined));
- console.log(a.call(null));
- 补充:基本数据类型赋值的时候 赋的是具体的值 引用数据类型传的是地址,一个变另一个跟着变
js数据类型转换
代码语言:javascript复制在JavaScript中类型转换有三种情况: 转换为数字(调用Number(),parseInt(),parseFloat()方法) 转换为字符串(调用.toString()或String()方法) 转换为布尔值(调用Boolean()方法) 还有隐式转换 注意:null、undefined没有.toString方法
- 转换为数字
- Number():可以把任意值转换成数字,如果要转换的字符串中有不是数字的值,则会返回NaN
- Number('1') // 1
- Number(true) // 1
- Number('123s') // NaN
- Number({}) //NaN
- parseInt(string,radix):解析一个字符串并返回指定基数的十进制整数,radix是2-36之间的整数,表示被解析字符串的基数。
- parseInt('2') //2
- parseInt('2',10) // 2
- parseInt('2',2) // NaN
- parseInt('a123') // NaN 如果第一个字符不是数字或者符号就返回NaN
- parseInt('123a') // 123
- parseFloat(string):解析一个参数并返回一个浮点数
- parseFloat('123a')
- //123
- parseFloat('123a.01')
- //123
- parseFloat('123.01')
- //123.01
- parseFloat('123.01.1')
- //123.01
- 隐式转换
- let str = '123'
- let res = str - 1 //122
- str 1 // '1231'
- str 1 // 124
- 转换为字符串
- .toString() ⚠️注意:null,undefined不能调用
- Number(123).toString()
- //'123'
- [].toString()
- //''
- true.toString()
- //'true'
- String() 都能转
- String(123)
- //'123'
- String(true)
- //'true'
- String([])
- //''
- String(null)
- //'null'
- String(undefined)
- //'undefined'
- String({})
- //'[object Object]'
- 隐式转换:当 两边有一个是字符串,另一个是其它类型时,会先把其它类型转换为字符串再进行字符串拼接,返回字符串
- let a = 1
- a '' // '1'
- 转换为布尔值
- 0, ''(空字符串), null, undefined, NaN会转成false,其它都是true
- Boolean()
- Boolean('') //false
- Boolean(0) //false
- Boolean(1) //true
- Boolean(null) //false
- Boolean(undefined) //false
- Boolean(NaN) //false
- Boolean({}) //true
- Boolean([]) //true
- 条件语句
- let a
- if(a) {
- //... //这里a为undefined,会转为false,所以该条件语句内部不会执行
- }
- 隐式转换 !!
- let str = '111'
- console.log(!!str) // true
- {}和[]的valueOf和toString的返回结果?
- valueOf:返回指定对象的原始值
- 对象 返回值
- Array 返回数组对象本身。
- Boolean 布尔值。
- Date 存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。
- Function 函数本身。
- Number 数字值。
- Object 对象本身。这是默认情况。
- String 字符串值。
- Math 和 Error 对象没有 valueOf 方法。
- toString:返回一个表示对象的字符串。默认情况下,toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,
- toString() 返回 "[object type]",其中 type 是对象的类型。
- ({}).valueOf() //{}
- ({}).toString() //'[object Object]'
- [].valueOf() //[]
- [].toString() //''
数据类型相比较objected .is ==和===
代码语言:javascript复制=== 属于严格判断,直接判断两者类型是否相同,如果两边的类型不一致时,不会做强制类型准换,不同则返回false如果相同再比较大小,不会进行任何隐式转换对于引用类型来说,比较的都是引用内存地址,所以===这种方式的比较,除非两者存储的内存地址相同才相等,反之false == 二等表示值相等。判断操作符两边对象或值是否相等类型可以不同,如果两边的类型不一致,则会进行强制类型转化后再进行比较,使用Number()转换成Number类型在进行判断。例外规则,null==undefined,null/undefined进行运算时不进行隐式类型转换。通常把值转为Boolean值,进行条件判断。Boolean(null)===Boolean(undefined)>false===false 结果为true Object.is()在===基础上特别处理了NaN,-0, 0,保证-0与 0不相等,但NaN与NaN相等
- ==操作符的强制类型转换规则
- 字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。
- 其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,再应用其他规则进行比较。
- null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。
- 对象和非对象之间的相等比较,对象先调用 ToPrimitive 抽象操作后,再进行比较。
- 如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。
- 如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true,否则,返回 false。
- '1' == 1 // true
- '1' === 1 // false
- NaN == NaN //false
- 0 == -0 //true
- 0 === -0 // true
- Object.is( 0,-0) //false
- Object.is(NaN,NaN) //true
typeof null 的结果是什么,为什么?
代码语言:javascript复制typeof null 的结果是Object。 在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits) 以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有五种数据类型:
- 000: object - 当前存储的数据指向一个对象。
- 1: int - 当前存储的数据是一个 31 位的有符号整数。
- 010: double - 当前存储的数据指向一个双精度的浮点数。
- 100: string - 当前存储的数据指向一个字符串。
- 110: boolean - 当前存储的数据是布尔值。
有两种特殊数据类型:
- undefined的值是 (-2)30(一个超出整数范围的数字);
- null 的值是机器码 NULL 指针(null 指针的值全是 0)
那也就是说null的类型标签也是000,和Object的类型标签一样,所以会被判定为Object。
事件的故事
什么是事件?
事件是文档和浏览器窗口中发生的特定的交互瞬间,事件就发生了。 一是直接在标签内直接添加执行语句, 二是定义执行函数。 addeventlistener 监听事件 事件类型分两种:事件捕获、事件冒泡。 事件捕获就是:网景公司提出的事件流叫事件捕获流,由外往内,从事件发生的顶点开始,逐级往下查找,一直到目标元素。 事件冒泡:IE提出的事件流叫做事件冒泡就是由内往外,从具体的目标节点元素触发,逐级向上传递,直到根节点。 什么是事件流? 事件流就是,页面接受事件的先后顺序就形成了事件流。 自定义事件 自定义事件,就是自己定义事件类型,自己定义事件处理函数。
事件委托
事件委托,又名事件代理。事件委托就是利用事件冒泡,就是把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托也就没法实现了 阻止事件冒泡 event.stopPropagation() .stop修饰符 addEventListener(‘click',函数名,true/false) 默认值为false(即 使用事件冒泡)true 事件捕获 好处:提高性能,减少了事件绑定,从而减少内存占用 应用场景 在vue中事件委托: 我们经常遇到vue中v-for一个列表,列表的每一项都绑定了@click处理事件。我们都知道绑定这么多监听,从性能方面来说是不太好的。那我们我们可以通过把每个item的click事件委托给父元素的形式来实现
封装事件绑定
我们在封装这个函数的时候可以用addEventListener(事件监听)来实现 ,封装的函数有三个参数,第一个是要绑定事件的元素,第二个是要绑定的事件类型,第三个是事件的执行函数。调用这个函数 就可以实现给某个元素绑定一个事件了。
Javascript 的作用域和作用域链
作用域: 作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找。简单说:函数内部局部作用域,函数外面全局作用域。
作用域就是一个变量可以使用的范围,主要分为全局作用域和函数作用域 全局作用域就是Js中最外层的作用域,在哪里都可以访问 函数作用域是js通过函数创建的一个独立作用域,只能在函数内部访问,函数可以嵌套,所以作用域也可以嵌套 Es6中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等)
防抖节流
防抖:所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。 节流:所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。两种方式可以实现,分别是时间戳版和定时器版。
鼠标事件 mouseenter与mouseover区别
mouseenter:鼠标进入被绑定事件监听元素节点时触发一次,再次触发是鼠标移出被绑定元素,再次进入时。而当鼠标进入被绑定元素节点触发一次后没有移出,即使鼠标动了也不再触发。 mouseover:鼠标进入被绑定事件监听元素节点时触发一次,如果目标元素包含子元素,鼠标移出子元素到目标元素上也会触发。 mouseenter 不支持事件冒泡 mouseover 会冒泡
引用数据类型 object
object的方法
代码语言:javascript复制
- Object.is() 是一种判断两个值是否相同的方法。
- 语法:Object.is(value1, value2);
- 参数:value1:要比较的第一个值。value2:要比较的第二个值。
- 返回值:一个布尔表达式,指示两个参数是否具有相同的值。
- Object.assign() 方法用于将所有可枚举的自身属性从一个或多个源对象复制到目标对象。
- 语法:Object.assign(target, ...sources)
- 参数:target:目标对象——应用源属性的对象,修改后返回。sources:源对象——包含你要应用的属性的对象。
- 返回值:修改后的目标对象。
- Object.entries() ES8的Object.entries是把对象转成键值对数组, [key, value] 对的数组。
- 语法:Object.entries(obj)
- 参数:obj:要返回其自己的可枚举字符串键属性 [key, value] 对的对象。返回值:给定对象自己的可枚举字符串键属性 [key, value] 对的数组。
- Object.fromEntries则相反,是把键值对数组转为对象
- Object.values() 方法返回给定对象自己的可枚举属性值的数组,其顺序与 for...in 循环提供的顺序相同。
- 语法:Object.values(obj)
- 参数:obj:要返回其可枚举自身属性值的对象。返回值:包含给定对象自己的可枚举属性值的数组。
- Object.prototype.hasOwnProperty()
- hasOwnProperty() 方法返回一个布尔值,指示对象是否具有指定的属性作为它自己的属性。
- 如果指定的属性是对象的直接属性,则该方法返回 true — 即使值为 null 或未定义。如果该属性是继承的或根本没有声明,则返回 false。
- 语法:hasOwnProperty(prop)
- 参数:prop:要测试的属性的字符串名称或符号。
- 返回值:如果对象将指定的属性作为自己的属性,则返回true;否则为false。
- Object.keys()
- Object.keys() 方法用于返回给定对象自己的可枚举属性名称的数组,以与普通循环相同的顺序迭代。
- 语法:Object.keys(obj)
- 参数:obj:要返回可枚举自身属性的对象。
- 返回值:表示给定对象的所有可枚举属性的字符串数组。
- Object.prototype.toString()
- toString() 方法返回一个表示对象的字符串。当对象将被表示为文本值或以期望字符串的方式引用对象时,将自动调用此方法 id。默认情况下,toString() 方法由从 Object 继承的每个对象继承。
- 语法:toString()
- 返回值:表示对象的字符串。
- Object.freeze()
- Object.freeze() 方法冻结一个对象,这意味着它不能再被更改。冻结对象可防止向其添加新属性,防止删除现有属性,防止更改现有属性的可枚举性、可配置性或可写性,并防止更改现有属性的值。它还可以防止其原型被更改。
- 语法:Object.freeze(obj)
- 参数:obj:要冻结的对象。返回值:传递给函数的对象。
- Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。(请打开浏览器控制台以查看运行结果。)
- 语法:const me = Object.create(person);
- 参数:
- proto:新创建对象的原型对象。
- propertiesObject
- 可选。需要传入一个对象,该对象的属性类型参照Object.defineProperties()的第二个参数。如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
- 返回值
- 一个新对象,带着指定的原型对象和属性。
对象和面向对象
对象:属性和方法的集合叫做对象(万物皆对象)。 面向对象:首先就是找对象,如果该对象不具备所需要的方法或属性,那就给它添加。面向对象是一种编程思维的改变。通过原型的方式来实现面向对象编程。 创建对象的方式(4种):new Object、字面量、构造函数、原型。
什么是深拷贝,浅拷贝,浅拷贝 赋值的区别,如何实现
深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝。 1.浅拷贝:
- 将原对象或原数组的引用直接赋给新对象,新数组,新对象只是对原对象的一个引用,而不复制对象本身,新旧对象还是共享同一块内存
- 如果属性是一个基本数据类型,拷贝就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,
2.深拷贝:
- 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”
- 深拷贝就是把一个对象,从内存中完整的拷贝出来,从堆内存中开辟了新区域,用来存新对象,并且修改新对象不会影响原对象
3、赋值: 当我们把一个对象赋值给一个新的变量时,赋的是该对象在栈中的内存地址,而不是堆中的数据。也就是两个对象
具体实现看开头的手写系列
代码语言:javascript复制
- 浅拷贝的实现方式:
- 1、object.assign()
- 2、lodash 里面的 _.clone
- 3、...扩展运算符
- 4、 Array.prototype.concat
- 5、 Array.prototype.slice
- 深拷贝的实现方式
- 1、 JSON.parse(JSON.stringify())
- 2、递归操作
- 3、cloneDeep
- 4、Jquery.extend()
数组
数组的方法
代码语言:javascript复制
- 1、sort( ):sort 排序 如果下面参数的正反 控制 升序和降序 ,返回的是从新排序的原数组
- 2、splice( ):向数组的指定index处插入 返回的是被删除掉的元素的集合,会改变原有数组;截取类 没有参数,返回空数组,原数组不变;一个参数,从该参数表示的索引位开始截取,直至数组结束,返回截取的 数组,原数组改变;两个参数,第一个参数表示开始截取的索引位,第二个参数表示截取的长度,返回截取的 数组,原数组改变;三个或者更多参数,第三个及以后的参数表示要从截取位插入的值。会改变原数据
- 3、pop( ):从尾部删除一个元素 返回被删除掉的元素,改变原有数组。
- 4、push( ):向数组的末尾追加 返回值是添加数据后数组的新长度,改变原有数组。
- 5、unshift( ):向数组的开头添加 返回值是添加数据后数组的新长度,改变原有数组。
- 6、shift( ):从头部删除一个元素 返回被删除掉的元素,改变原有数组。
- 7、reverse( ):原数组倒序 它的返回值是倒序之后的原数组
- 8、concat( ):数组合并。
- 9、slice( ):数组元素的截取,返回一个新数组,新数组是截取的元素,可以为负值。从数组中截取,如果不传参,会返回原数组。如果只传入一个参数,会从头部开始删除,直到数组结束,原数组不会改变;传入两个参数,第一个是开始截取的索引,第二个是结束截取的索引,不包含结束截取的这一项,原数组不会改变。最多可以接受两个参数。
- 10、join( ):讲数组进行分割成为字符串 这能分割一层在套一层就分隔不了了
- 11、toString( ):数组转字符串;
- 12、toLocaleString( ):将数组转换为本地数组。
- 13、forEach( ):数组进行遍历;
- 14、map( ):没有return时,对数组的遍历。有return时,返回一个新数组,该新数组的元素是经过过滤(逻辑处理)过的函数。
- 15、filter( ):对数组中的每一运行给定的函数,会返回满足该函数的项组成的数组。
- 16、every( ):当数组中每一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
- 17、some( ):当数组中有一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
- 18、reduce( ):回调函数中有4个参数。prev(之前计算过的值),next(之前计算过的下一个的值),index,arr。把数组列表计算成一个
- 19.isArray() 判断是否是数组
- 20. indexOf 找索如果找到了就会返回当前的一个下标,若果没找到就会反回-1
- 21. lastIndexOf 它是从最后一个值向前查找的 找索如果找到了就会返回当前的一个下标,若果没找到就会反回-1
- 22. Array.of() 填充单个值
- 23. Array.from() 来源是类数组
- 24.fill填充方法 可以传入3各参数 可以填充数组里的值也就是替换 如果一个值全部都替换掉 , 第一个参数就是值 第二个参数 从起始第几个 第三个参数就是最后一个
- find 查找这一组数 符合条件的第一个数 给他返回出来
- findIndex() 查找这一组数 符合条件的第一数的下标 给他返回出来 没有返回 -1
- keys 属性名 values属性值 entries属性和属性值
- forEach 循环遍历 有3个参数 无法使用 break continue , 参数一就是每个元素 参数二就是每个下标 参数三就是每个一项包扩下标和元素
- ### 改变数组本身的api
- 1. `pop()` 尾部弹出一个元素
- 2. `push()` 尾部插入一个元素
- 3. `shift()` 头部弹出一个元素
- 4. `unshift()` 头部插入一个元素
- 5. `sort([func])` 对数组进行排序,func有2各参数,其返回值小于0,那么参数1被排列到参数2之前,反之参数2排在参数1之前
- 6. `reverse()` 原位反转数组中的元素
- 7. `splice(pos,deleteCount,...item)` 返回修改后的数组,从pos开始删除deleteCount个元素,并在当前位置插入items
- 8. `copyWithin(pos[, start[, end]])` 复制从start到end(不包括end)的元素,到pos开始的索引,返回改变后的数组,浅拷贝
- 9. `arr.fill(value[, start[, end]])` 从start到end默认到数组最后一个位置,不包括end,填充val,返回填充后的数组
- 其他数组api不改变原数组
- map 映射关系的数组 map 主要就是有返回值可以return 数组 判断的会返回boolean
- 1、map()方法返回一个新数组,新数组中的元素为原始数组中的每个元素调用函数处理后得到的值。
- 2、map()方法按照原始数组元素顺序依次处理元素。
- 注意:
- map()不会对空数组进行检测。
- map()不会改变原始数组。
- map() 函数的作用是对数组中的每一个元素进行处理,返回新的元素。
- filter 满足条件的都能返回 是一个数组
- some返回boolean 循环数组 只要有一个成员通过了就会返回 true 反而 false
- every返回boolean 循环数组 只有全部成员通过了就会返回 true 反而 false
- reduce() 累加器 把上一次计算的值,给下一次计算进行相加
- set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用
- delete [1] delete 可以删除数组中的一向
- **Array.isArray()** 用于确定传递的值是否是一个 [`Array`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array)。
- flat 扁平化 将嵌套的数组 “拉平”,变成一维数组。该方法返回一个新数组,对原数据没有影响。// 参数写的就是代表要扁平到第几层
- //1、every()
- var arr = [1,56,80,5];
- var main = arr.every(n => n > 0);
- console.log(main) //输出:true
- //2、some()
- var arr = [1,-56,80,-5];
- var main = arr.some(n => n > 0);
- console.log(main) //输出:true
- //3、reducer()
- var arr = [10,20,30,40]
- let result = arr.reduce(function(prev,next,index,arr){
- return prev next;
- })
- console.log(result); //输出:100
- // 4、filter 返回满足要求的数组项组成的新数组
- var arr3 = [3,6,7,12,20,64,35]
- var result3 = arr3.filter((item,index,arr)=>{
- return item > 3
- })
- console.log(result3) //[6,7,12,20,64,35]
- // 5、map 返回每次函数调用的结果组成的数组
- var arr4 = [1,2]
- var result4 = arr4.map((item,index,arr)=>{
- return `<span>${item}</span>`
- })
- console.log(result4)
- /*[ '<span>1</span>',
- '<span>2</span>', ]*/
- ES6数组的常用方法:
- 1、Array.from( ):将对象或字符串转成数组,注意得有length。
- 2、Array.of( ):将一组值转换为数组。
- 3、copyWithin(target,start(可选),end(可选)):数组内数据的复制替换
- target:从该位置开始替换数据;
- start:从该位置开始读取数据,默认为0;
- end:到该位置停止数据的读取,默认为数组的长度
- 4、find( ):用于找出第一个符合条件的数组成员。
- 5、findIndex( ):返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
- 6、fill(value,start,end):使用给定值,填充一个数组。
- value:填充的值;
- start:开始填充的位置;
- end:填充结束的位置。
- 7、keys( ):对键名的遍历。
- 8、values( ):对键值的遍历。
- 9、entries( ):对键值对的遍历。
- 10、includes( ):数组原型的方法,查找一个数值是否在数组中,只能判断一些简单类型的数据,对于复杂类型的数据无法判断。该方法接受两个参数,分别是查询的数据和初始的查询索引值。
- 11、flat( ):用于数组扁平,数组去除未定义。可以去除空项。
- 12、flatMap( ):对原数组的每个成员执行一个函数。
- 13、Map( ):是一组键值对的结构,具有极快的查找速度。
- 14、Set( ):Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。
- //1、Array.from() -- Array.of()
- var arrayLink = {
- "0":"a",
- "1":"b",
- "2":"c",
- length:3
- }
- var arr = Array.from(arrayLink)
- console.log(arr) // 输出: [a,b,c]
- console.log(Array.from("abcdefg")) //输出:["a", "b", "c", "d", "e", "f", "g"]
- console.log(Array.of(1,2,3,4,5)) //输出: [1, 2, 3, 4, 5]
- //2、copyWithin()
- var arr = [1,2,3,4,5];
- var main = arr.copyWithin(0,3);
- console.log(main); //输出:[4,5,3,4,5]
- //3、find()
- var arr = [1,-5,2,9,-6];
- var main = arr.find(n => n < 0);
- console.log(main); //输出:-5
- //4、fill()
- var arr = ["a","b","c","d"];
- console.log(arr.fill(7,1,2));//输出:["a",7,"c","d"]
- //5、keys() values() entries()
- var arr = ["a","b","c","d"];
- for(let index of arr.keys()){
- console.log(index);
- }
- for(let elem of arr.values()){
- console.log(elem);
- }
- for(let [index,elem] of arr.entries()){
- console.log(index,elem);
- }
- //6、includes()
- let arr = [12,34,223,45,67]
- console.log(arr.includes(45)) //输出:true
- [1, 2, NaN].includes(NaN) // true
- [1, 2, NaN].indexOf(NaN) // -1
- //7、Map
- var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
- m.get('Michael'); // 95
- //初始化Map需要一个二维数组,或者直接初始化一个空Map。Map具有以下方法:
- var m = new Map(); // 空Map
- m.set('Adam', 67); // 添加新的key-value
- m.set('Bob', 59);
- m.has('Adam'); // 是否存在key 'Adam': true
- m.get('Adam'); // 67
- m.delete('Adam'); // 删除key 'Adam'
- m.get('Adam'); // undefined
- //由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
- var m = new Map();
- m.set('Adam', 67);
- m.set('Adam', 88);
- m.get('Adam'); // 88
- //8、Set
- //要创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set:
- var s1 = new Set(); // 空Set
- var s2 = new Set([1, 2, 3]); // 含1, 2, 3
- //重复元素在Set中自动被过滤:
- var s = new Set([1, 2, 3, 3, '3']);
- s; // Set {1, 2, 3, "3"} 注意:数字3和字符串'3'是不同的元素
- //通过add(key)方法可以添加元素到Set中,可以重复添加,但不会有效果:
- s.add(4);
- s; // Set {1, 2, 3, 4}
- s.add(4);
- s; // 仍然是 Set {1, 2, 3, 4}
- //通过delete(key)方法可以删除元素:
- var s = new Set([1, 2, 3]);
- s; // Set {1, 2, 3}
- s.delete(3);
- s; // Set {1, 2}
字符串
字符串的方法
代码语言:javascript复制
- 1、chartAt( ):返回在指定位置的字符;
- 2、concat( ):返回新的字符串**,将一个或多个字符串与原字符串连接合并
- 3、indexOf( ):检索字符串,返回第一次出现的索引,没有出现则为-1
- 4、lastIndexOf(searchValue[ fromIndex]) 返回从字符串尾部开始第一次出现的索引,没有则-1,fromIndex的值相对于从尾部开始的索引
- 5、split( ):返回一个以指定分隔符出现位置分隔而成的一个数组,数组元素不包含分隔符
- 6、substr( ):从起始索引号提取字符串中指定数目的字符;
- 7、substring( ):提取字符串中两个指定的索引号之间的字符;
- 8、toLowerCase( ):字符串转小写;
- 9、toUpperCase( ):字符串转大写;
- 10、valueOf( ):返回某个字符串对象的原始值;
- 11、trim( ):删除字符串两边的空格;
- 12、trimeState 取出开始的空格
- 13、trimeEnd 去除末尾空格
- 14、includes(searchString[, position])返回boolean,判断一个字符串是否包含在另一个字符串中,从postition索引开始搜寻,默认0
- 15、slice( ):提取字符串片段,并在新的字符串中返回被提取的部分;
- 16、search(regexp)返回首次匹配到的索引,没有则-1,执行正则表达式和 String 对象之间的一个搜索匹配
- 17、toString()返回一个表示调用对象的字符串,该方法返回指定对象的字符串形式
- 18、trim()返回去掉两端空白后的新字符串 还有trimend trimstart
- 19、replace() 把指定的字符串替换成为别的字符
超长字符串存储到栈内存中
字符串
属于基础类型
,所以会觉得字符串
是存在栈内存
中的,但是要知道,V8默认栈内存是984Kib
,那如果一个超长字符串 > 984Kib
能装的进栈内存
吗? 字符串的内容存于堆内存中,指针存于栈内存中,且相同的字符串指向同一个堆内存地址 新增或者修改字符串后,如果是一个之前不存在的字符串,则新开辟内存空间,如果是已有的,则直接使用已有的内存空间 当我们新建一个字符串时,V8会从内存中查找一下是否已经有存在的一样的字符串,找到的话直接复用。如果找不到的话,则开辟一块新的内存空间来存这个字符串,并把地址赋给变量。
javascript函数
- 声明函数的几种方式
- 函数声明
- function 函数名(参数1,参数2,...){ //要执行的语句 }
- 函数表达式
- var func2=function(b){}//函数表达式
- var func3=function func4(c){}//命名式函数表达式
- var func5=(function(n1,n2){})();//立即执行的函数表达式
- return function(){ };//作为返回值的函数表达式
- Function构造器
- var 变量名 = new Function("参数1","参数2",...,"参数n","函数体");
- 立即执行函数
- var func5=(function(n1,n2){})();//立即执行的函数表达式 ()()
- 函数声明与函数表达式的区别
函数声明会将那个函数提升到最前面(即使你写代码的时候在代码块最后才写这个函数),成为全局函数。 函数声明要指定函数名,而函数表达式不用,可以用作匿名函数。
- 函数调用的几种方式
1.直接调用 函数名加上括号 () 2.函数表达式 变量名()
- 函数的长度
代码语言:javascript复制函数的
length
属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length
属性将失真。
- function fun1(a) { }
- function fun2(a, b) { }
- function fun3(a, b, c) { }
- function fun4(a, b, c, d) { }
- function fun5(...args) { }
- function fun6(a = 1, b, c, d) { }
- console.log(fun1.length) // 1
- console.log(fun2.length) // 2
- console.log(fun3.length) // 3
- console.log(fun4.length) // 4
- console.log(fun5.length) // 0
- console.log(fun6.length) // 0
- 立即执行函数(iife)和使用场景
立即执行函数:( function( ){ })( ) 返回值可以为基本数据类型,也能返会任何类型的值。 写法原因:因为在 javascript 里,括号内部不能包含语句,当解析器对代码进行解释的时候,先碰到了(), 然后碰到function关键字就会自动将()里面的代码识别为函数表达式而不是函数声明。 作用:立即执行函数会形成一个单独的作用域,我们可以封装一些临时变量或者局部变量,避免污染全局变量。 使用场景:①代码在页面加载完成之后,不得不执行一些设置工作,比如时间处理器,创建对象等等。②所有的这些工作只需要执行一次,比如只需要显示一个时间。 ③需要一些临时的变量,但是初始化过程结束之后,就再也不会被用到,我们可以用立即执行函数——去将我们所有的代码包裹在它的局部作用域中, 不会让任何变量泄露成全局变量。
arguments 的对象是什么?
arguments 当我们不知道有多少个参数传进来的时候就用 arguments 来接收,是一个类似于数组的对象,他有length属性,可以arguments[ i ]来访问对象中的元素, 但是它不能用数组的一些方法。 例如push、pop、slice等。arguments虽然不是一个数组,但是它可以转成一个真正的数组。 取之可以用 展开运算符来 数组和类数组类数组:①拥有length属性,其它属性(索引)为非负整数;箭头函数里没有arguments ②不具有数组所具有的方法;③类数组是一个普通对象,而真实的数组是Array类型。 常见的类数组:arguments,document.querySelectorAll得到的列表,jQuery对象($("div"));
this指向的问题(高频)
在全局的环境下this是指向window 的 普通函数调用直接调用中的this 会指向 window, 严格模式下this会指向 undefined,自执行函数 this 指向 window,定时器中的 this 指向 window 在对象里调用的this,指向调用函数的那个对象, 在构造函数以及类中的this,构造函数配合 new 使用, 而 new 关键字会将构造函数中的 this 指向实例化对象,所以构造函数中的 this 指向 当前实例化的对象 方法中的this谁调用就指向谁。 箭头函数没有自己的 this,箭头函数的this在定义的时候,会继承自外层第一个普通函数的this
函数式编程含义:
函数式编程是一种强调以函数为主的软件开发风格。通过组合纯函数,避免共享状态、可变作用和副作用来构建软件的过程。目的:使用函数来抽象作用在数据之上的控制流和操作,从而在系统中消除副作用并减少对状态的改变。
闭包
代码语言:javascript复制1、闭包的概念就是:只有权利访问另一个函数作用域中的变量,一般就是函数包裹着函数。 3、闭包可以重用一个变量,且保证这个变量不会被污染的一种机制。这些变量的值始终保持在内存中,不会被垃圾回收机制处理 4、闭包的缺点:由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。 5、为什么要用闭包:使用场景 : 防抖、节流、函数套函数避免全局污染
- 闭包原理
- 函数执行分成两个阶段(预编译阶段和执行阶段)。
- 1.在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应变量值,
- 如果已存在“闭包”,则只需要增加对应属性值即可。
- 2.执行完后,函数执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但其内部函数还持用该“闭包”的引用,
- 所以内部函数可以继续使用“外部函数”中的变量
- 利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁,
- 但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被烧毁后才被销毁。
call、apply、bind封装与区别
都是来改变this指向和函数的调⽤,实际上call与apply的功能是相同的,只是两者的传参方式不一样, call⽅法跟的是⼀个参数列表, apply跟⼀个 数组作为参数,call⽅法和apply使⽤后就直接调⽤ bind 传参后不会立即执行,而是返回一个改变了this指向的函数,这个函数可以继续传参,且执行,需要类似于bind()()两个括号才能调⽤。
- call 的性能要比apply好一点(尤其是当函数传递参数超过3个的时候)后期开发 call 多多一点
- call 用扩展运算符就可以吧 apply 来代替了
bind 返回的函数可以作为构造函数吗?
不可以,会报错的哦, ERROR > Uncaught TypeError: s is not a constructor
函数柯里化(卡瑞化、加里化)?
概念:把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。容易理解的概念:Currying概念其实很简单,只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数(主要是利用闭包实现的)。 特点: ①接收单一参数,将更多的参数通过回调函数来搞定; ②返回一个新函数,用于处理所有的想要传入的参数; ③需要利用call/apply与arguments对象收集参数; ④返回的这个函数正是用来处理收集起来的参数。 作用:能进行部分传值,而传统函数调用则需要预先确定所有实参。如果你在代码某一处只获取了部分实参,然后在另一处确定另一部分实参,这个时候柯里化和偏应用就能派上用场。 用途:我认为函数柯里化是对闭包的一种应用形式,延迟计算、参数复用、动态生成函数(都是闭包的用途)。
柯里化函数例子
代码语言:javascript复制柯里化函数:把一个多参数的函数转化为单参数函数的方法。并且返回接受余下的参数而且返回结果的新函数的技术。 我的理解就是将一个接受多个参数的函数,转化为接收一个参数,并且不改变输出结果的一种办法。我觉得这就是js的柯里化函数
- // 简单的相加函数
- var add = function (x,y) {
- return x y
- }
- // 调用:
- add(1,2)
- // 柯里化以后
- var add = function (x) { //柯里化函数(闭包)
- return function (y) {
- return x y
- }
- }
- add(1)(2)
这样做有什么好处,我得理解是在需要的情况下生成一个中间工具,简化代码,并且清晰代码。
什么是高阶函数?
代码语言:javascript复制高阶函数只是,将函数作为参数 , 函数的返回值返回值是函数
- function higherOrderFunction(param,callback){
- return callback(param);
- }
构造函数
new的原理
代码语言:javascript复制
- new实际上是在堆内存中开辟一个空间。
- ①创建一个空对象,构造函数中的this指向这个空对象;
- ②这个新对象被执行[ [ 原型 ] ]连接;
- ③执行构造函数方法,属性和方法被添加到this引用的对象中;
- ④如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。
- function _new(){
- let target = {}; //创建的新对象
- let [constructor,...args] = [...arguments];
- //执行[[原型]]连接,target是constructor的实例
- target.__proto__ = constructor.prototype;
- //执行构造函数,将属性或方法添加到创建的空对象上
- let result = constructor.prototype;
- if(result && (typeof (result) == "object" || typeof (result) == "function")){
- //如果构造函数执行的结构返回的是一个对象,那么返回这个对象
- return result;
- }
- //如果构造函数返回的不是一个对象,返回创建的对象
- return target;
- }
- 自己理解的new:
- new实际上是在堆内存中开辟一个新的空间。首先创建一个空对象obj,然后呢,
- 把这个空对象的原型(__proto__)和构造函数的原型对象(constructor.prototype)连接(说白了就是等于);
- 然后执行函数中的代码,就是为这个新对象添加属性和方法。最后进行判断其返回值,如果构造函数返回的是一个对象,
- 那就返回这个对象,如果不是,那就返回我们创建的对象。
封装一个通用的事件绑定函数
代码语言:javascript复制
- 需要点击每个a,来。弹出他们的内容
- <div id="div3">
- <a href="#">a1</a><br>
- <a href="#">a2</a><br>
- <a href="#">a3</a><br>
- <a href="#">a4</a><br>
- <button id='btn1'>加载更多...</button>
- </div>
- // 封装通用的事件绑定函数
- function bindEvent(elem, type, fn) {
- elem.addEventListener(type, fn)
- }
- //获取父元素
- const fu = document.getElementById('div3')
- bindEvent(fu, 'click', function (event) {
- // console.log(event.target) // 获取触发的元素
- let target=event.target
- event.preventDefault() // 阻止默认行为
- //过滤符合条件的子元素,主要是过滤掉 加载更多
- if(target.nodeName.toLowerCase()==="A"){
- alert(target.innerHTML;
- }
- })
作用域,js的机制
垃圾回收机制和内存机制
垃圾回收 浏览器的js具有自动垃圾回收机制,垃圾回收机制也就是自动内存管理机制,垃圾收集器会定期的找出那些不在继续使用的变量,然后释放内存。但是这个过程不是实时的,因为GC开销比较大并且时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。 内存泄露 如果 那些不再使用的变量,它们所占用的内存 不去清除的话就会造成内存泄漏 内存泄露其实就是我们的程序中已经动态分配的堆内存,由于某些原因没有得到释放,造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果。 比如说: 1、闭包:在闭包中引入闭包外部的变量时,当闭包结束时此对象无法被垃圾回收(GC)。 2、DOM:当原有的DOM被移除时,子结点引用没有被移除则无法回收 3、Times计时器泄露
作用域
1、作用域 作用域就是一个变量可以使用的范围,主要分为全局作用域和函数作用域 全局作用域就是Js中最外层的作用域 函数作用域是js通过函数创建的一个独立作用域,函数可以嵌套,所以作用域也可以嵌套 Es6中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等) 2、自由变量 当前作用域外的变量都是自由变量,一个变量在当前作用域没有定义,但是被使用了,就会向上级作用域,一层一层依次查找,直至找到为止,如果全局作用域都没有找到这个变量就会报错。这个自由变量查找的过程就是作用域链。 3、变量提升 每个var声明的变量,function声明的函数存在变量提升。let const不存在变量提升 在js中声明之前未定义,会在js的最上方会形成一个预解析池,用来存储声明了但没有先定义的变量名 4、作用域链: 作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和 函数 , 简单来说:内部函数访问外部函数的变量这种链式查找的机制被称为作用域链
谈谈JS的运行机制
1. js单线程
JavaScript语言的一大特点就是单线程,即同一时间只能做一件事情。
2. js事件循环
js代码执行过程中会有很多任务,这些任务总的分成两类:
- 同步任务
- 异步任务
需要注意的是除了同步任务和异步任务,任务还可以更加细分为macrotask(宏任务)和microtask(微任务),js引擎会优先执行微任务
代码语言:javascript复制
- 微任务包括了 promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。
- 宏任务包括了 script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲
- 染等。
- 首先js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。
- 在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务
- 当同步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。
- 任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。
- 当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。
最后可以用下面一道题检测一下收获:
代码语言:javascript复制
- setTimeout(function() {
- console.log(1)
- }, 0);
- new Promise(function(resolve, reject) {
- console.log(2);
- resolve()
- }).then(function() {
- console.log(3)
- });
- process.nextTick(function () {
- console.log(4)
- })
- console.log(5)
第一轮:主线程开始执行,遇到setTimeout,将setTimeout的回调函数丢到宏任务队列中,在往下执行new Promise立即执行,输出2,then的回调函数丢到微任务队列中,再继续执行,遇到process.nextTick,同样将回调函数扔到为任务队列,再继续执行,输出5,当所有同步任务执行完成后看有没有可以执行的微任务,发现有then函数和nextTick两个微任务,先执行哪个呢?process.nextTick指定的异步任务总是发生在所有异步任务之前,因此先执行process.nextTick输出4然后执行then函数输出3,第一轮执行结束。 第二轮:从宏任务队列开始,发现setTimeout回调,输出1执行完毕,因此结果是25431
JS延迟加载的方式
JavaScript 是单线程(js不走完下面不会走是因为同步)会阻塞DOM的解析,因此也就会阻塞DOM的加载。所以有时候我们希望延迟JS的加载来提高页面的加载速度。 1.把JS放在页面的最底部 2.script标签的defer属性:脚本会立即下载但延迟到整个页面加载完毕再执行。该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。 3.是在外部JS加载完成后,浏览器空闲时,Load事件触发前执行,标记为async的脚本并不保证按照指定他们的先后顺序执行, 该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。 4.动态创建script标签,监听dom加载完毕再引入js文件
宏任务和微任务
代码语言:javascript复制js中的一个机制,就是遇到宏任务,先将宏任务放入eventqueue,然后在执行微任务。 宏任务:setTimeout,setInterval,Ajax,DOM事件 微任务:Promise async/await 想明白这个机制 就要理解js单线程。因为JS是单线程语言,只能同时做一件事儿。js任务需要排队顺序执行,如果一个任务时间过长,后边的任务也会等着。假如,我们在请求一个网址时,图片加载很慢,网页总不能一直卡不出来, 这个时候就可以用异步来解决了,异步的特点不会阻塞代码的执行 ,解决了单线程等待的这个问题 在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务 异步和单线程是相辅相成的,js是一门单线程语言,所以需要异步来辅助。
- 宏任务macrotask:可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到
- 执行栈中执行)。
- 常见的宏任务:script, setTimeout, setInterval, setImmediate, I/O, UI rendering。
- 微任务microtask(异步):可以理解是在当前task执行结束后立即执行的任务。
- 常见的微任务:process.nextTick(Nodejs),Promise.then(), MutationObserver。
- 线程,进程?
- 线程是最小的执行单元,进程是最小的资源管理单元一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程
内存泄露 如果 那些不再使用的变量,它们所占用的内存 不去清除的话就会造成内存泄漏 比如说: 1、闭包:在闭包中引入闭包外部的变量时,当闭包结束时此对象无法被垃圾回收(GC)。 2、DOM:当原有的DOM被移除时,子结点引用没有被移除则无法回收 3、Times计时器泄露
JS预解析(变量提升),它导致了什么问题?
JS代码在执行前,浏览器会对js代码进行扫描,默认的把所有带var和function声明的变量进行提前的声明或者定义,遵循先解析后使用的原则。 变量提升的表现是,在变量或函数声明之前访问变量或调用函数而不会报错。 原因 JavaScript引擎在代码执行前有一个解析的过程(预编译),创建执行上线文,初始化一些代码执行时需要用到的对象。当访问一个变量时,会到当前执行上下文中的作用域链中去查找,而作用域链的首端指向的是当前执行上下文的变量对象,这个变量对象是执行上下文的一个属性, 它包含了函数的形参、所有的函数和变量声明,这个对象的是在代码解析的时候创建的。 首先要知道,JS在拿到一个变量或者一个函数的时候,会有两步操作,即解析和执行。 1.在解析阶段 JS会检查语法,并对函数进行预编译。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来, 变量先赋值为undefined,函数先声明好可使用。在一个函数执行之前,也会创建一个函数执行上下文环境,跟全局执行上下文类似, 不过函数执行上下文会多出this、arguments和函数的参数。 全局上下文:变量定义,函数声明 函数上下文:变量定义,函数声明,this,arguments
2.在执行阶段,就是按照代码的顺序依次执行。
代码语言:javascript复制
- 那为什么会进行变量提升呢?主要有以下两个原因:
- 1、提高性能
- 2、容错性更好
- (1)提高性能 在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,
- 那么每次执行代码前都必须重新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。
- 在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计声明了哪些变量、创建了哪些函数,并对函数的代码进行压缩,去除注释、
- 不必要的空白等。这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间(不需要再解析一遍去获取代码中声明了哪些变量,创建了哪些函数),
- 并且因为代码压缩的原因,代码执行也更快了。
- (2)容错性更好 变量提升可以在一定程度上提高JS的容错性,看下面的代码:
- a = 1
- var a
- console.log(a) //1
- 如果没有变量提升,这段代码就会报错导致的问题
- var tmp = new Date();
- function fn(){
- console.log(tmp);
- if(false){
- var tmp = 'hello nanjiu';
- }
- }
- fn(); // undefined
- 在这个函数中,原本是要打印出外层的tmp变量,但是因为变量提升的问题,内层定义的tmp被提到函数内部的最顶部,
- 相当于覆盖了外层的tmp,所以打印结果为undefined。
- var tmp = 'hello nan jiu';
- for (var i = 0; i < tmp.length; i ) {
- console.log(tmp[i]);
- }
- console.log(i); // 13
- 由于遍历时定义的i会变量提升成为一个全局变量,在函数结束之后不会被销毁,所以打印出来13。
- 总结
- 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
- 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行
- 函数是一等公民,当函数声明与变量声明冲突时,变量提升时函数优先级更高,会忽略同名的变量声明
服务端渲染
解释:服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。有了服务端渲染,当请求用户页面时,返回的body里已经有了首屏的html结构,之后结合css显示出来。 优点: ①首屏渲染快(关键性问题):相比于加载单页应用,我只需要加载当前页面的内容,而不需要像 React 或者 Vue 一样加载全部的 js 文件; ②SEO(搜索引擎)优化:不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本 ③可以生成缓存片段、节能; 缺点:用户体验较差,不容易维护、通常前端改了部分html或者css,后端也需要改; 使用场景:vue全家桶或者react全家桶,都是推荐通过服务端渲染来实现路由的。
Event Loop Event Queue
在js中我们经常需要同时执行很多件任务,例如,定时器,事件。异步数据,而js是单线程的原因不能同时进行很多件事情,必须等上一件任务执行完了才会执行下一个,需要通过Event Loop 来处理很多任务的执行 因为js是单线程的,代码执行的时候,将不同的函数执行上下文压入到栈中进行有序的执行, 在执行同步代码的时候,如果遇到了异步事件,js引擎并不会一直等待其返回结果,就是将它挂起,继续执行栈中其他的任务 当同步任务执行完了,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。 任务队列分为的宏任务队列和微任务队列,当前的执行栈中执行完,js引擎会首先判断微任务队列是否有任务可以执行有的话,放到栈中执行。 当微任务队列中的任务执行完了再去判断宏任务中的队列。 为什么会有任务队列呢? 还是因为 javascript 单线程的原因,单线程,就意味着一个任务一个任务的执行, 执行完当前任务,执行下一个任务,这样也会遇到一个问题,就比如说,要向服务端通信,加载大量数据,如果是同步执行, js 主线程就得等着这个通信完成,然后才能渲染数据,为了高效率的利用cpu, 就有了同步任务和异步任务之分。
同步和异步的区别?各举一个Js中同步和异步的案例?
同步:上一件事情没有完成,继续处理上一件事情,只有上一件事情完成了,才会做下一件事情 异步:规划要做一件事情,如果是异步事情,不是当前立马去执行这件事情,需要等一定的时间,这样的话,我们不会等着他执行,而是继续执行下面的操作 对于写程序,同步往往会阻塞,没有数据过来,我就等着,异步则不会阻塞,没数据来我干别的事,有数据来去处理这些数据。 同步案例:for循环语句,alert(),console.log()等 js大部分都是同步编程 异步案例:所有定时器,ajax异步请求,所有的事件绑定都是异步; 举例子 同步,就是实时处理(如打电话),比如服务器一接收客户端请求,马上响应,这样客户端可以在最短的时间内得到结果,但是如果多个客户端,或者一个客户端发出的请求很频繁,服务器无法同步处理,就会造成涌塞。 同步如打电话,通信双方不能断(我们是同时进行,同步),你一句我一句,这样的好处是,对方想表达的信息我马上能收到,但是,我在打着电话,我无法做别的事情。 异步,就是分时处理(如收发短信),服务器接收到客户端请求后并不是立即处理,而是等待服务器比较空闲的时候加以处理,可以避免涌塞。
BOM浏览器对象模型
js操作BOM
浏览器对象模型(BOM :Browser Object Model)是JavaScript的组成之一,它提供了独立于内容与浏览器窗口进行交互的对象,使用浏览器对象模型可以实现与HTML的交互。它的作用是将相关的元素组织包装起来,提供给程序设计人员使用,从而降低开发人员的劳动量,提高设计Web页面的能力。 window : alert() , prompt() , confirm() , setInterval() , clearInterval() , setTimeout() , clearTimeout() ; history : go(参数) , back() , foward() ; location : herf属性. 1、window.location.href = '你所要跳转到的页面'; 2、window.open('你所要跳转到的页面’); 3、window.history.back(-1):返回上一页 4、window.history.go(-1/1):返回上一页或下一页五、 5、history.go("baidu.com");
说出5个以上Math对象中的成员。
Math.PI 圆周率 Math.floor() 向下取整 Math.ceil() 向上取整 Math.round() 四舍五入版 就近取整 Math.abs() 绝对值 Math.max()/Math.min() 求最大和最小值 Math.random() 获取范围在[0,1)内的随机值
setTimeout与setInterval区别与机制
代码语言:javascript复制setTimeout()和setInterval()经常被用来处理延时和定时任务。 setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式 setInterval()则可以在每隔指定的毫秒数循环调用函数或表达式,直到clearInterval把它清除。
- 机制:
- 因为js是单线程的。浏览器遇到setTimeout 和 setInterval会先执行完当前的代码块,在此之前会把定时器推入浏览器的
- 待执行时间队列里面,等到浏览器执行完当前代码之后会看下事件队列里有没有任务,有的话才执行定时器里的代码
window的onload事件和domcontentloaded
window.onload:当一个资源及其依赖资源已完成加载时,将触发onload事件。document.onDOMContentLoaded:当初始的HTML文档被完全加载和解析完成之后, DOMContentLoaded事件被触发,而无需等待样式表、图像和子框架的完成加载。区别:①onload事件是DOM事件,onDOMContentLoaded是HTML5事件。②onload事件会被样式表、图像和子框架阻塞,而onDOMContentLoaded不会。③当加载的脚本内容并不包含立即执行DOM操作时,使用onDOMContentLoaded事件是个更好的选择,会比onload事件执行时间更早。
cookies,sessionStorage 和 localStorage 的区别?
cookie:一个大小不超过4K的小型文本数据,一般由服务器生成,可以设置失效时间;若没有设置时间,关闭浏览器cookie失效,若设置了 时间,cookie就会存放在硬盘里,过期才失效,每次http请求,header都携带cookie localStorage:5M或者更大,永久有效,窗口或者浏览器关闭也会一直保存,除非手动永久清除或者js代码清除,因此用作持久数据,不参与和服务器的通信 sessionStorage关闭页面或浏览器后被清除。存 放数据大小为一般为 5MB,而且它仅在客户端(即浏览器)中保存,不参与和服务器的通信。
location、之lnavigator和history
location 对象存储了当前文档位置(URL)相关的信息,简单地说就是网页地址字符串。使用 window 对象的 location 属性可以访问。 href会重新定位到一个URL,hash会跳到当前页面中的anchor名字的标记(如果有),而且页面不会被重新加载
history
window 对象给我们提供了一个 history 对象,与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中) 访问过的 URL。 history.back 可以后退一个网页 history.go 可以前进后退 1前进 -1 后退 history.forward 前进
navigator对象
window.navigator`对象包含有关浏览器的信息,可以用它来查询一些关于运行当前脚本的应用程序的相关信息 navigator.appCodeName 只读,任何浏览器中,总是返回 'Gecko'。该属性仅仅是为了保持兼容性。
navigator.appName
只读,返回浏览器的官方名称。不要指望该属性返回正确的值。navigator.appVersion
只读,返回一个字符串,表示浏览器的版本。不要指望该属性返回正确的值。navigator.platform
只读,返回一个字符串,表示浏览器的所在系统平台。navigator.product
只读,返回当前浏览器的产品名称(如,"Gecko")。navigator.userAgent
只读,返回当前浏览器的用户代理字符串(user agent string)
DOM文档对象模型
DOM是 document 用来表示文档中对象的标准模型,他是由节点和对象组成的结构集合。在浏览器解析HTML标签时,会构建一个DOM树结构。
操作说明书
代码语言:javascript复制
- 拿到指定节点
- var id = document.getElementById("id"); //返回带有指定id的元素
- var name = document.getElementByTagName("li"); //返回带有指定标签的元素
- var class = document.getElementByClassName("class"); //返回带有包含执行类名的所有元素节点列表。`
- 创建DOM节点
- var node = document.createElement("div");
- var attr = document.createAttribute("class");
- var text = document.createTextNode("菜呀菜");`
- 插入DOM节点
- node.appendChild(text) //插入新的子节点
- node.insertBefore(pre,child) //在node元素内child前加入新元素`
- 删除DOM节点
- node.removeChild(text) //从父元素删除子元素节点
- 修改DOM节点
- node.setAttribute("class","name") //修改设置属性节点
- node.replaceChild(pre,child) //父节点内新子节点替换旧子节点`
- 常用DOM属性
- node.innerHtml //获取/替换元素内容
- node.parentNode //元素节点的父节点
- node.parentElement //元素节点的父元素节点(一般与Node节点相同)
- node.firstChild //属性的第一个节点
- node.lastChild //属性的最后一个节点
- node.nextSibling //节点元素后的兄弟元素(包括回车,空格,换行)
- node.nextElementSibling //节点元素后的兄弟元素节点
- node.previousSibling //获取元素的上一个兄弟节点(元素,文本,注释)
- node.previousElementSibling //获取元素的上一个兄弟节点(只包含元素节点)
- node.childNodes //元素节点的子节点(空格,换行默认为文本节点)
- node.children //返回当前元素的所有元素节点
- node.nodeValue //获取节点值
- node.nodeName //获取节点名字
- node.attributes //元素节点的属性节点
- node.getAttribute("name") //元素节点的某个属性节点
- node.style.width = "200px" //设置css样式`
常用的api
offset、client、scroll的用法?
offset系列 经常用于获得元素位置 offsetLeft offsetTop client经常用于获取元素大小 clientWidth clientHeight scroll 经常用于获取滚动距离 scrollTop scrollLeft
js面试题的扩展
什么是函数式编程? 命令式编程?声明式编程?
声明式编程:专注于”做什么”而不是”如何去做”。在更高层面写代码,更关心的是目标,而不是底层算法实现的过程。如:css, 正则表达式,sql 语句,html, xml… 命令式编程(过程式编程) : 专注于”如何去做”,这样不管”做什么”,都会按照你的命令去做。解决某一问题的具体算法实现。 如:for() 函数式编程:把运算过程尽量写成一系列嵌套的函数调用。 如 :forEach()
iframe的优缺点有哪些?
优点: ①iframe能够原封不动的把嵌入的网页展现出来; ②如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。 ③网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。 ④如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。 缺点: ①会产生很多页面不易管理; ②iframe框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。 ③代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理iframe中的内容,所以使用iframe会不利于搜索引擎优化。 ④很多的移动设备(PDA 手机)无法完全显示框架,设备兼容性差。 ⑤iframe框架页面会增加服务器的http请求,对于大型网站是不可取的。
如何让(a == 1 && a == 2 && a == 3)的值为true?
代码语言:javascript复制
- " == "操作符在左右数据不一致的时候,会先进行隐式转换,该值意味着不是基本数据类型,
- 因为如果a是null或者undefined、bool类型都不可能返回true;可以推测a是复杂数据类型。
- 方法一:数组的 toString 接口默认调用数组的 join 方法,重新 join 方法
- let a = [1,2,3];
- a.join = a.shift;
- console.log(a == 1 && a == 2 && a == 3) //true
- 方法二:利用数据劫持(Proxy/Object.definedProperty)
- let i = 1;
- let a = new Proxy({},{
- i:1,
- get:function(){
- return () => this.i
- }
- });
- console.log(a == 1 && a == 2 && a == 3);
为什么0.1 0.2 ! == 0.3,如何让其相等
代码语言:javascript复制
- 在开发过程中遇到类似这样的问题:
- let n1 = 0.1, n2 = 0.2
- console.log(n1 n2) // 0.30000000000000004
- 这里得到的不是想要的结果,要想等于0.3,就要把它进行转化:
- (n1 n2).toFixed(2) // 注意,toFixed为四舍五入
- 复制代码
- toFixed(num) 方法可把 Number 四舍五入为指定小数位数的数字。那为什么会出现这样的结果呢?
- 计算机是通过二进制的方式存储数据的,所以计算机计算0.1 0.2的时候,实际上是计算的两个数的二进制的和
es6部i分面试题
1、 ES6 新增特性
- 新增了块级作用域(let,const)
- 提供了定义类的语法糖(class)
- 新增了一种基本数据类型(Symbol)
- 新增了变量的解构赋值
- 函数参数允许设置默认值,引入了 rest 参数,新增了箭头函数
- 数组新增了一些 API,如 isArray / from / of 方法;数组实例新增了entries(),keys() 和 values() 等方法
- 对象和数组新增了扩展运算符
- ES6 新增了模块化(import/export)
- ES6 新增了 Set 和 Map 数据结构
- ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例
- ES6 新增了生成器(Generator)和遍历器(Iterator)
2、require与import的区别和使用(CommonJS规范和es6规范)
1、import是ES6中的语法标准也是用来加载模块文件的,import函数可以读取并执行一个JavaScript文件,然后返回该模块的export命令指定输出的代码。export与export default均可用于导出常量、函数、文件、模块,export可以有多个,export default只能有一个。 2、require 定义模块:module变量代表当前模块,它的exports属性是对外的接口。通过exports可以将模块从模块中导出,其他文件加载该模块实际上就是读取module.exports变量,他们可以是变量、函数、对象等。在node中如果用exports进行导出的话系统会系统帮您转成module.exports的,只是导出需要定义导出名。 require与import的区别 1,require是CommonJS规范的模块化语法,import是ECMAScript 6规范的模块化语法; 2,require是运行时加载,import是编译时加载; 3,require可以写在代码的任意位置,import只能写在文件的最顶端且不可在条件语句或函数作用域中使用; 4,require通过module.exports导出的值就不能再变化,import通过export导出的值可以改变; 5;require通过module.exports导出的是exports对象,import通过export导出是指定输出的代码; 6,require运行时才引入模块的属性所以性能相对较低,import编译时引入模块的属性所所以性能稍高。
3、箭头函数
js中我们在调⽤函数的时候经常会遇到this作⽤域的问题,这个时候ES6给我们提箭头函数。 1、 箭头函数是匿名函数不能作为构造函数,不能使用new 2、 箭头函数不绑定arguments,取而代之用rest参数…解决, 3、 this指向不同,箭头函数的this在定义的时候继承自外层第一个普通函数的this 4、 箭头函数通过call()或apply()调用一个函数,只传入了一个参数,对this并没有影响. 5、 箭头函数没有prototype(原型),所以箭头函数本身没有this 6、 箭头函数不能当做Generator函数,不能使用yield关键字、 7、 写法不同,箭头函数把function省略掉了 ()=> 也可以吧return 省略调 写法更简洁 8、箭头函数不能通过call()、apply()、bind()方法直接修改它的this指向。
4、简述 let const var 的区别 以及使用场景
- var let 是用来声明变量的,而const是声明常量的 var
代码语言:javascript复制
1.var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined
2、一个变量可多次声明,后面的声明会覆盖前面的声明
3、在函数中使用var声明变量的时候,该变量是局部的作用域只在函数内部,而如果在函数外部使用 var,该变量是全局的
- let
代码语言:javascript复制
1、不存在变量提升,let声明变量前,该变量不能使用。就是 let 声明存在暂时性死区
2、let命令所在的代码块内有效,在块级作用域内有效,作用域只是在花括号里面
3、let不允许在相同作用域中重复声明,注意是相同作用域,不同作用域有重复声明不会报错
- const
代码语言:javascript复制
1、const声明一个只读的常量,声明后,值就不能改变
2、let和const在同一作用域不允许重复声明变量const声明一个只读的常量。一旦声明,常量的值就不能改变,但对于对象和数据这种 引用类型,内存地址不能修改,可以修改里面的值。
3、let和const不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错
4、能用const的情况下尽量使用const,大多数情况使用let,避免使用var。const > let > var const声明的好处,一让阅读代码的人知道该变量不可修改,二是防止在修改代码的过程中无意中修改了该变量导致报错,减少bug的产生
5、map和forEach的区别
相同点 都是循环遍历数组中的每一项 forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组),需要用哪个的时候就写哪个 匿名函数中的this都是指向window 只能遍历数组 注意:forEach对于空数组是不会调用回调函数的。
不同点 map方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。(原数组进行处理之后对应的一个新的数组。) map()方法不会改变原始数组 map()方法不会对空数组进行检测 forEach()方法用于调用数组的每个元素,将元素传给回调函数.(没有return,返回值是undefined)
6、promise的解释
1、Promise 是异步编程的一种解决方案,主要用于异步计算,支持链式调用,可以解决回调地狱 的问题,自己身上有all、reject、resolve、race 等方法,原型上有then、catch等方法。 2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果,可以在对象之间传递和操作 promise,帮助我们处理队列 3、promise 有三个状态:pending[待定]初始状态,fulfilled[实现]操作成功,rejected[被否决]操作失败 4、Promise 对象状态改变:从
pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了 5、如果不设置回调函数,Promise内部抛出的错误,不会反应到外部,但是写了then 和 catch ,会被then的第二个参数 或 catch所捕获
- promise 的 then 为什么可以支持链式调用
promise 的then会返回一个新的 promise 对象,能保证 then 方 可以进行链式调用
补充:
Promise.all哪怕一个请求失败了也能得到其余正确的请求结果的解决方案
代码语言:javascript复制
- Promise.all默认只要有一个错误就直接返回错误。promise.all中任何一个promise 出现错误的时候都会执行reject,导致其它正常返回的数据也无法使用
- Promise.all(
- [
- Promise.reject({ code: 500, msg: "服务异常" }),
- Promise.resolve({ code: 200, list: [] }),
- Promise.resolve({ code: 200, list: [] })
- ].map(p => p.catch(e => e))
- )
- .then(res => {
- console.log("res=>", res);
- })
- .catch(error => {
- console.log("error=>", error);
- });
- res=> [ { code: 500, msg: '服务异常' },
- { code: 200, list: [] },
- { code: 200, list: [] } ]
- 核心内容是map方法,map的每一项都是promise,catch方法返回值会被promise.reslove()包裹,这样传进promise.all的数据都是resolved状态的。
- // 使用Promise.all 其中id为69的商品,返回失败,会导致整个Promise接受到reject状态.
- // 所以进行改造, p catch 得到的err 为返回失败抛出的信息, 进行置空
- .map(p => p.catch(err => '')))
6、async、await的原理
Async 和 await 是一种同步的写法,但还是异步的操作,两个必须配合一起使用 函数前面的
async
关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise
对象。 await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西,如果是promise则会等待promaise 返回结果,接普通函数直接进行链式调用. await 能够获取promise执行的结果 await必须和async一起使用才行,async配合await使用是一个阻塞的异步方法 如果await后面不是Promise对象, 就直接返回对应的值,只能在async函数中出现, 普通函数直接使用会报错 await语句后的Promise对象变成reject状态时,那么整个async函数会中断,后面的程序不会继续执行
使用场景:
我在项目中:需求:执行第一步,将执行第一步的结果返回给第二步使用。在ajax中先拿到一个接口的返回数据,然后使用第一步返回的数据执行第 二步操作的接口调用,达到异步操作。
7、解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值 常见的几种方式有 1.默认值 2.交换变量 3.将剩余数组赋给一个变量 结构数组和对象字符串区别 对象的解构与数组类似,但有所不同。数组的元素是按次序排列的,变量的取值由它的位置决定; 而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。字符串也是可以解构赋值的。字符串被转换成了一个类似数组的对象. 我在项目中:就是从目标对象或数组中提取自己想要的变量。最常用的场景是:element-ui,vant-ui按需引入,请求接口返回数据,提取想要数据。
8、 for...in 迭代和 for...of 有什么区别
1、 推荐在循环对象属性的时候,使用 for...in,在遍历数组的时候的时候使用for...of。 2、 for in遍历的是数组的索引,而for of遍历的是数组元素值 3、for...of 不能循环普通的对象,需要通过和 Object.keys()搭配使用 4、for...in 遍历顺序以数字为先 无法遍历 symbol 属性 可以遍历到公有中可枚举的 5、从遍历对象的角度来说,for···in会遍历出来的为对象的key,但for···of会直接报错。
9、 generator 有了解过吗?
- Generator 生成器 也是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同 function *(){}
- Generator 函数是一个状态机,封装了多个内部状态,除了状态机,还是一个遍历器对象生成函数。
- Generator 是分段执行的, yield (又得)可暂停,next方法可启动。每次返回的是yield后的表达式结果,这使得
Generator
函数非常适合将异步任务同步化 - Generator
并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署
Interator`接口…) Generator
函数返回Iterator
对象,因此我们还可以通过for...of
进行遍历,原生对象没有遍历接口,通过Generator
函数为它加上这个接口,就能使用for...of
进行遍历了
promise、Generator、async/await进行比较:
promise和async/await是专门用于处理异步操作的 Generator并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator接口…) promise编写代码相比Generator、async更为复杂化,且可读性也稍差 Generator、async需要与promise对象搭配处理异步情况 async实质是Generator的语法糖,相当于会自动执行Generator函数 async使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案
10、js构造函数的静态成员和实例成员
js的构造函数(在别的后台语言上叫做类)上可以添加一些成员,可以在构造函数内部的this上添加,可以在构造函数本身上添加,通过这两种方式添加的成员,就分别称为实例成员和静态成员 实例成员:构造函数中this上添加的成员 静态成员:构造函数本身上添加的成员 实例成员,只能由实例化的对象来访问 静态成员,只能由构造函数本身来访问 实例化对象的proto指向构造函数的prototype属性指向的对象,实例化的对象可以访问到它后者身上的成员
构造函数生成实例的执行过程:使用面向对象编程时,new关键字做了什么?
- 新建了一个Object对象
- 修改构造函数this的指向,是其指向新建的Object对象,并且执行构造函数
- 为Object对象添加了一个proto属性,是其指向构造函数的prototype属性
- 将这个Object对象返回出去
11、set和map数据结构有哪些常用的属性和方法?
set数据的特点是数据是唯一的
代码语言:javascript复制
- const set1 = new Set()
- 增加元素 使用 add
- set2.add(4)
- 是否含有某个元素 使用 has
- console.log(set2.has(2))
- 查看长度 使用 size
- console.log(set2.size)
- 删除元素 使用 delete
- set2.delete(2)
- size: 返回Set实例的成员总数。
- add(value):添加某个值,返回 Set 结构本身。
- delete(value):删除某个值。
- clear():清除所有成员,没有返回值。
Set
的不重复性
- 传入的数组中有重复项,会自动去重
- const set2 = new Set([1, 2, '123', 3, 3, '123'])
- Set`的不重复性中,要注意`引用数据类型和NaN
- 两个对象都是不用的指针,所以没法去重
- const set1 = new Set([1, {name: '孙志豪'}, 2, {name: '孙志豪'}])
- 如果是两个对象是同一指针,则能去重
- const obj = {name: '我们一样'}
- const set2 = new Set([1, obj, 2, obj])
- NaN !== NaN,NaN是自身不等于自身的,但是在Set中他还是会被去重
- const set = new Set([1, NaN, 1, NaN])
map数据结构
代码语言:javascript复制Map`对比`object`最大的好处就是,key不受`类型限制
- 定义map
- const map1 = new Map()
- 新增键值对 使用 set(key, value)
- map1.set(true, 1)
- 判断map是否含有某个key 使用 has(key)
- console.log(map1.has('哈哈'))
- 获取map中某个key对应的value
- console.log(map1.get(true))
- 删除map中某个键值对 使用 delete(key)
- map1.delete('哈哈')
- 定义map,也可传入键值对数组集合
- const map2 = new Map([[true, 1], [1, 2], ['哈哈', '嘻嘻嘻']])
- console.log(map2) // Map(3) { true => 1, 1 => 2, '哈哈' => '嘻嘻嘻' }
12、proxy 的理解
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。 Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
13、Es6中新的数据类型symbol
symbol 是es6 加入的,是一个基本数据类型,它代表的是一个独一无二的值,SYMBOL 值是由 SYMBOL函数生成,也就是说现在我们定义对象的属性名字可以是原有的字符串 也可以是 symbol 类型的,symbol 可以保证不与其他属性名冲突,减少了bug的产生, 如果那 symbol 对比的话 就是会返回 false symbol 他是一个原始类型的值就,不可以使用 new 关键字,symbol不是对象 没有迭代器的接口 不能去添加属性值,他是类似于字符串的一种类型 symbol 不能用来四则运算,否则会报错,只能用显示的方式转为字符串 symbol 参数里的 a 表示一种修饰符 对当前创建的 symbol 的一种修饰,作为区分 ,否则会混淆
14、iterator == iteration (遍历器的概念)
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作 Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令
for...of
循环,Iterator 接口主要供for...of
消费。 其实iteration == iterator 有三个作用:
- 为各种数据结构,提供一个统一的、简便的访问接口;
- 使得数据结构的成员能够按某种次序排列;
- 主要供
for...of
消费
15、Object.assign
Object.assign可以实现对象的合并。它的语法是这样的:
Object.assign(target, ...sources)
Object.assign
会将source里面的可枚举属性复制到target
。如果和target的已有属性重名,则会覆盖。同时后续的source会覆盖前面的source的同名属性。 Object.assign复制的是属性值,如果属性值是一个引用类型,那么复制的其实是引用地址,就会存在引用共享的问题Array.from()
方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。 那么什么是类数组对象呢?所谓类数组对象,最基本的要求就是具有length属性的对象。
1、将类数组对象转换为真正数组:
代码语言:javascript复制
- let arrayLike = {
- 0: 'tom',
- 1: '65',
- 2: '男',
- 3: ['jane','john','Mary'],
- 'length': 4
- }
- let arr = Array.from(arrayLike)
- console.log(arr) // ['tom','65','男',['jane','john','Mary']]
代码语言:javascript复制那么,如果将上面代码中
length
属性去掉呢?实践证明,答案会是一个长度为0的空数组。 这里将代码再改一下,就是具有length
属性,但是对象的属性名不再是数字类型的,而是其他字符串型的,代码如下:
- let arrayLike = {
- 'name': 'tom',
- 'age': '65',
- 'sex': '男',
- 'friends': ['jane','john','Mary'],
- length: 4
- }
- let arr = Array.from(arrayLike)
- console.log(arr) // [ undefined, undefined, undefined, undefined ]
会发现结果是长度为4,元素均为 undefined 的数组 由此可见,要将一个类数组对象转换为一个真正的数组,必须具备以下条件: 1、该类数组对象必须具有
length
属性,用于指定数组的长度。如果没有length
属性,那么转换后的数组是一个空数组。 2、该类数组对象的属性名必须为数值型或字符串型的数字
16、谈谈你对模块化开发的理解?
我对模块的理解是,一个模块是实现一个特定功能的一组方法。在最开始的时候,js 只实现一些简单的功能,所以并没有模块的概念 ,但随着程序越来越复杂,代码的模块化开发变得越来越重要。 由于函数具有独立作用域的特点,最原始的写法是使用函数来作为模块,几个函数作为一个模块,但是这种方式容易造成全局变量的污 染,并且模块间没有联系。 后面提出了对象写法,通过将函数作为一个对象的方法来实现,这样解决了直接使用函数作为模块的一些缺点,但是这种办法会暴露所 有的所有的模块成员,外部代码可以修改内部属性的值。 现在最常用的是立即执行函数的写法,通过利用闭包来实现模块私有作用域的建立,同时不会对全局作用域造成污染。
17、js 的几种模块规范?
js 中现在比较成熟的有四种模块加载方案:
- 第一种是 CommonJS 方案,它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。
- 第二种是 AMD 方案,这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。
- 第三种是 CMD 方案,这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。
- 第四种方案是 ES6 提出的方案,使用 import 和 export 的形式来导入导出模块。
加油快通关了,通关了你会有收获的。
下篇预告:前端面试题 --- Vue部分