この記事では、Web フロントエンドの面接で収集する価値のある厳選された質問をいくつかまとめています (回答付き)。一定の参考値があるので、困っている友達が参考になれば幸いです。
基本データ型
ES5 の 5 種類: Null、unknown、Boolean、Number、String、 ES6 の新機能: シンボルは一意の値を表します。 ES10 の新機能: BigInt は任意の大きな整数を表します。
参照データ型 : (本質的に、上記は次のように構成されます)順序付けされていないキーと値のペアのセット)
参照データ型 : Object。オブジェクト、配列、関数、日付、正規表現が含まれます。 JavaScript はカスタム タイプのデータの作成をサポートしていません。つまり、JavaScript のすべての値は上記の 8 つのタイプのいずれかになります。
同じ:
if ステートメントでは、null と未定義は false に変換され、等価演算子と比較すると等価になります。
まず、Unknown と Null はどちらも基本的なデータ型であり、これら 2 つの基本的なデータ型にはそれぞれ、unknown と null という値が 1 つだけあります。
は異なります:
未定義は未定義を意味し、
は仮パラメータを定義し、それを渡しません。実引数は、一般変数が宣言されているが定義されていない場合に、未定義の変数が返されます。オブジェクトの属性名が存在しない場合、関数が記述していない場合は、未定義の
#が表示されます。 return value. 、つまり、戻り値は書き込まれず、取得されるものは unknownnull (空のオブジェクトを意味します) になります。オブジェクト プロトタイプ チェーンのエンドポイントとしても機能します。
null は主に、初期化としてオブジェクトを返す可能性のあるいくつかの変数に値を割り当てるために使用されます。ES10 の新機能: BigInt は任意の大きな整数を表します
Numberデータ型は、より広範囲の整数値をサポートします。任意の精度で整数を表現できる機能は、大きな整数に対して数学演算を実行する場合に特に重要です。 BigInt
data type ストレージとスタック メモリとは何ですか?を使用すると、整数のオーバーフローは問題なくなります。
さらに、より正確なタイムスタンプや大きな整数 ID などは、回避策を使用しなくても安全に使用できます。 BigInt は現在フェーズ 3 の提案です。仕様に追加されると、JS の 2 番目の数値データ型になり、JS の 8 番目の基本データ型にもなります:
要创建BigInt,只需在整数的末尾追加n即可。比较: console.log(9007199254740995n); // → 9007199254740995n console.log(9007199254740995); // → 9007199254740996 或者,可以调用BigInt()构造函数 BigInt("9007199254740995"); // → 9007199254740995n // 注意最后一位的数字 9007199254740992 === 9007199254740993; // → true console.log(9999999999999999); // → 10000000000000000ログイン後にコピー
stackスタック:メモリに格納された単純なデータ セグメントを参照します。数値文字列ブール値 参照データ型: スタック メモリとヒープ メモリに同時に格納され、大きなスペースを占有し、固定されません。サイズ。 参考データ: ポインタをスタックに、値をヒープに格納する型です。オブジェクトの値を別の変数に割り当てると、コピーされるのは、同じメモリ アドレスを指すオブジェクトのポインタです。 は、変数に保存されているのは実際には単なるポインタであり、このポインタは、メモリ ヒープ、配列オブジェクトの実際の値
ヒープとスタックの違いは何ですか?ストレージ メカニズム
ヒープ: は、キュー優先順位、先入れ先出しを備えた非連続ツリー ストレージ データ構造です。; 各ノードには値があり、ツリー全体がソートされます。特徴は、ルート ノードの値が最小 (または最大) であり、ルート ノードの 2 つのサブツリーもヒープであることです。これは優先キューの実装によく使用され、自由にアクセスできます。
#jsのデータ型判定、条件分岐
データ型判定&& 逻辑与 两边都是true,才返回true,否则返回false || 逻辑或 两边只要有一个是true,就返回true,否则返回false ! 逻辑非 用来取一个布尔值相反的值ログイン後にコピー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)); 补充:基本数据类型赋值的时候 赋的是具体的值 引用数据类型传的是地址,一个变另一个跟着变ログイン後にコピー
转换为字符串(调用.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() //''
=== 属于严格判断,直接判断两者类型是否相同,如果两边的类型不一致时,不会做强制类型准换,不同则返回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 的结果是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(事件监听)来实现 ,封装的函数有三个参数,第一个是要绑定事件的元素,第二个是要绑定的事件类型,第三个是事件的执行函数。 调用这个函数 就可以实现给某个元素绑定一个事件了。
作用域: 作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找。简单说:函数内部局部作用域,函数外面全局作用域。
作用域就是一个变量可以使用的范围,主要分为全局作用域和函数作用域
全局作用域就是Js中最外层的作用域,在哪里都可以访问
函数作用域是js通过函数创建的一个独立作用域,只能在函数内部访问,函数可以嵌套,所以作用域也可以嵌套
Es6中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等)
防抖:所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
节流:所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。两种方式可以实现,分别是时间戳版和定时器版。
mouseenter: 鼠标进入被绑定事件监听元素节点时触发一次,再次触发是鼠标移出被绑定元素,再次进入时。而当鼠标进入被绑定元素节点触发一次后没有移出,即使鼠标动了也不再触发。
mouseover: 鼠标进入被绑定事件监听元素节点时触发一次,如果目标元素包含子元素,鼠标移出子元素到目标元素上也会触发。
mouseenter 不支持事件冒泡 mouseover 会冒泡
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、赋值:
当我们把一个对象赋值给一个新的变量时,赋的是该对象在栈中的内存地址,而不是堆中的数据。也就是两个对象
具体实现看开头的手写系列
浅拷贝的实现方式: 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()
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}
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能装的进<code>栈内存
吗?字符串的内容存于堆内存中,指针存于栈内存中,且相同的字符串指向同一个堆内存地址
新增或者修改字符串后,如果是一个之前不存在的字符串,则新开辟内存空间,如果是已有的,则直接使用已有的内存空间
当我们新建一个字符串时,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.函数表达式 变量名()
函数的长度
函数的
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
函数式编程含义:
函数式编程是一种强调以函数为主的软件开发风格。通过组合纯函数,避免共享状态、可变作用和副作用来构建软件的过程。 目的:使用函数来抽象作用在数据之上的控制流和操作,从而在系统中消除副作用并减少对状态的改变。
闭包
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对象收集参数;
④返回的这个函数正是用来处理收集起来的参数。
作用:能进行部分传值,而传统函数调用则需要预先确定所有实参。如果你在代码某一处只获取了部分实参,然后在另一处确定另一部分实参,这个时候柯里化和偏应用就能派上用场。
用途:我认为函数柯里化是对闭包的一种应用形式,延迟计算、参数复用、动态生成函数(都是闭包的用途)。
柯里化函数例子
柯里化函数:把一个多参数的函数转化为单参数函数的方法。并且返回接受余下的参数而且返回结果的新函数的技术。
我的理解就是将一个接受多个参数的函数,转化为接收一个参数,并且不改变输出结果的一种办法。我觉得这就是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)ログイン後にコピー这样做有什么好处,我得理解是在需要的情况下生成一个中间工具,简化代码,并且清晰代码。
什么是高阶函数?
高阶函数只是,将函数作为参数 , 函数的返回值返回值是函数
function higherOrderFunction(param,callback){ return callback(param); }ログイン後にコピー构造函数
new的原理
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)连接(说白了就是等于); 然后执行函数中的代码,就是为这个新对象添加属性和方法。最后进行判断其返回值,如果构造函数返回的是一个对象, 那就返回这个对象,如果不是,那就返回我们创建的对象。ログイン後にコピー封装一个通用的事件绑定函数
需要点击每个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引擎会优先执行微任务
微任务包括了 promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。 宏任务包括了 script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲 染等。ログイン後にコピー
首先js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。
在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务
当同步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。
任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。
当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。
最后可以用下面一道题检测一下收获:
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文件
宏任务和微任务
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.在执行阶段,就是按照代码的顺序依次执行。
那为什么会进行变量提升呢?主要有以下两个原因: 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全家桶,都是推荐通过服务端渲染来实现路由的。イベント ループ イベント キュー
js では、タイマーやイベントなど、多くのタスクを同時に実行する必要があることがよくあります。非同期データ、および js はシングルスレッドであるため、同時に多くのことを実行できません。次のタスクを実行する前に、前のタスクが実行されるまで待つ必要があります。多くのタスクの実行を処理するには、イベント ループを使用する必要があります
js はシングルスレッドであるため、コードが実行されると、順序よく実行するためにさまざまな関数実行コンテキストがスタックにプッシュされます。
同期コードを実行するときに、非同期イベントが発生すると、 JS エンジンは戻り結果を待つことは、それを一時停止し、スタック内の他のタスクの実行を継続することを意味します。
同期タスクが完了したら、非同期イベントに対応するコールバックを現在の実行スタックとは異なる別のタスク キューに追加します。そして待って実装してください。
タスクキューはマクロタスクキューとマイクロタスクキューに分かれており、現在の実行スタックで実行後、jsエンジンはまずマイクロタスクキューに実行可能なタスクがあるかどうかを判断し、あればタスクを投入します。実行のためにスタックに格納されます。
マイクロタスクキュー内のタスクが実行されると、マクロタスク内のキューが判定されます。
タスクキューがあるのはなぜですか?
これはやはり JavaScript のシングルスレッドが原因です。シングルスレッドとは、次のタスクを次々と実行することを意味します。
現在のタスクを実行した後、次のタスクを実行します。この場合も、次のような問題が発生します。たとえば、サーバーと通信して大量のデータをロードする必要があります。それが同期的に実行される場合、
js メイン スレッドは、データをレンダリングする前に通信が完了するまで待機する必要があります。 CPUを効率的に利用するために、同期タスクと非同期タスクがあります。同期と非同期の違いは何ですか? JS における同期と非同期の例を挙げてください。
同期: 前の処理は完了していません。前の処理を継続し、前の処理が完了した場合にのみ次の処理が実行されます。非同期: 何かを実行する予定です。非同期の場合はすぐには実行されず、一定時間待つ必要があります。この場合、実行を待たずに実行を続けます。次の操作#プログラムを書いていると同期がブロックすることが多い データがなければ待つ 非同期はブロックしない データがなければ他のことをする データがあればデータ加工させていただきます。
同期の場合: for ループ ステートメント、alert()、console.log() およびその他の JS はほとんどが同期プログラミングです
非同期の場合: すべてのタイマー、Ajax 非同期リクエスト、すべてのイベント バインディングはすべてです非同期;
例
同期はリアルタイム処理です (電話をかけるなど)。たとえば、サーバーはクライアント要求を受信するとすぐに応答します。クライアントはリクエストを最短時間で処理でき、一定時間内に結果が得られますが、複数のクライアントが存在する場合や、1つのクライアントからのリクエストが頻繁に行われる場合、サーバーは同期的にリクエストを処理できず、輻輳が発生します。
同期は電話をかけるのと似ています。通信中の 2 つの当事者は中断できません (同期は同時に行われます)。あなたが 1 つの文を言い、私がもう 1 つの文を言います。この利点は次のとおりです。相手が伝えたい情報をすぐに受け取ることができるのは良いのですが、通話中は他のことができません。
非同期とは、タイムシェアリング処理 (テキスト メッセージの送受信など) を意味します。サーバーはクライアントのリクエストを受信した後、すぐに処理せず、サーバーが比較的アイドル状態のときに処理されるのを待ちます。 、混雑を避けることができます。
BOM ブラウザ オブジェクト モデル
js 操作 BOMブラウザ オブジェクト モデル (BOM: Browser Object Model) は、JavaScript のコンポーネントの 1 つです。コンテンツとは独立してブラウザ ウィンドウと対話するオブジェクトを提供します。HTML との対話は、ブラウザ オブジェクト モデルを使用して実現できます。その機能は、関連する要素を整理およびパッケージ化してプログラム設計者に提供することにより、開発者の作業負荷を軽減し、Web ページの設計能力を向上させることです。
Math オブジェクトの 5 つ以上のメンバーの名前を指定します。ウィンドウ:alert()、prompt()、confirm()、setInterval()、clearInterval()、setTimeout()、clearTimeout();
history:go(パラメータ)、戻る(), foward() ;
location: herf 属性.
1. window.location.href = 'ジャンプしたいページ'; 2. window.open('Youジャンプしたいページ'); 3. window.history.back(-1): 前のページに戻ります 4. window.history.go(-1/1): 前または次のページに戻ります 5. 5 、history.go("baidu.com");
#Math.PI Pi
Math.floor() 切り捨てMath.ceil() 切り上げMath。 round() バージョンを最も近い整数に丸めますMath.abs()絶対値Math.max()/Math.min()最大値と最小値を検索しますMath.random() [0,1) の範囲のランダムな値を取得します。setTimeout と setInterval の違いとメカニズムsetTimeout() と setInterval() は、遅延タスクやスケジュールされたタスクを処理するためによく使用されます。
setTimeout
() メソッドは、指定されたミリ秒数が経過した後に関数を呼び出すか式を計算するために使用されます。setInterval
() は使用できます。 each 呼び出された関数または式を、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树结构。
操作说明书
拿到指定节点
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?
" == "操作符在左右数据不一致的时候,会先进行隐式转换,该值意味着不是基本数据类型, 因为如果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,如何让其相等
在开发过程中遇到类似这样的问题: 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部分面试题
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
1.var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined 2、一个变量可多次声明,后面的声明会覆盖前面的声明 3、在函数中使用var声明变量的时候,该变量是局部的作用域只在函数内部,而如果在函数外部使用 var,该变量是全局的ログイン後にコピー
- let
1、不存在变量提升,let声明变量前,该变量不能使用。就是 let 声明存在暂时性死区 2、let命令所在的代码块内有效,在块级作用域内有效,作用域只是在花括号里面 3、let不允许在相同作用域中重复声明,注意是相同作用域,不同作用域有重复声明不会报错ログイン後にコピー
- const
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哪怕一个请求失败了也能得到其余正确的请求结果的解决方案
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 の場合は、Promise が結果を返すまで待機します。 , 通常の関数に直接接続することができます. チェーン呼び出しを行う.
await は Promise の実行結果を取得できます. await は async と併用する必要があります. await と併用される Async はブロッキング非同期メソッド
await の後に Promise オブジェクトが続かない場合、対応する値が直接返されます。これは非同期関数でのみ使用できます。通常の関数は、次の場合にエラーを報告します。
await 文後の Promise オブジェクトが拒否状態になると、非同期関数全体が中断され、後続のプログラムは実行されなくなります
使用シナリオ:
私のプロジェクトの場合: 要件: 最初のステップを実行し、最初のステップの結果を 2 番目のステップに返して使用します。 ajax では、まずインターフェイスの戻りデータを取得し、次に最初のステップで返されたデータを使用して 2 番目のステップのインターフェイス呼び出しを実行することで、非同期操作を実現します。
7. 代入の構造化
ES6 では、特定の条件に従って配列やオブジェクトから値を抽出できます。パターンを使用して、変数に値を代入します。これは、代入の構造化と呼ばれます
一般的な方法は次のとおりです。
1.デフォルト値
2 .変数の交換
##3.残りの配列を変数に代入##構造体配列とオブジェクト文字列の違いObject の構造化は配列と似ていますが、異なります。配列の要素は順序どおりに配置され、変数の値はその位置によって決まります。
オブジェクトのプロパティは順序どおりではありません。正しい値を取得するには、変数の名前がプロパティと同じである必要があります。文字列を構造化して値を割り当てることもできます。文字列は配列のようなオブジェクトに変換されます。
#私のプロジェクトでは
: ターゲット オブジェクトまたは配列から必要な変数を抽出します。最も一般的に使用されるシナリオは次のとおりです。 element-ui と vant-ui はオンデマンドで導入され、インターフェイスにデータを返すように要求し、必要なデータを抽出します。8. for...in iteration と for...of
1 の違いは何ですか。ループする場合にお勧めします。オブジェクトのプロパティ、配列を走査するときに for...in を使用、for...of を使用します。
2. for in は配列のインデックスを走査し、for of は配列要素の値を走査します 3. for...of は通常のオブジェクトをループできず、Object を渡す必要があります.keys() を 4 で使用します。for...in 走査順序は数値が最初です。シンボル プロパティは走査できません。パブリック列挙可能な 5 まで走査できます。観点から見ると、言い換えると、for...in はオブジェクトのキーを走査しますが、for...of は直接エラーを報告します。 9. ジェネレーターについてご存知ですか?#Generator ジェネレーターも ES6 によって提供される非同期プログラミング ソリューションです。構文の動作は従来の関数 function *(){}
## とはまったく異なりますpromise と async/await は、非同期操作を処理するために特に使用されます
- #Generator 関数は、複数の内部状態をカプセル化するステート マシンであり、ステート マシンに加えて、トラバーサー オブジェクト生成関数でもあります。
- ジェネレーターはセクション単位で実行され、yield を一時停止して、次のメソッドを開始できます。 yield の後に式の結果を返すたびに、
Generator
関数は非同期タスクの同期に非常に適しています。- Interator` インターフェイスのデプロイメント...)
は設計されていません。非同期使用のために、他の機能もあります (オブジェクトの反復、制御出力、
Generator
- Iterator
この関数は
Generator
- オブジェクトを返すため、
for...of
を介してトラバースすることもできます。ネイティブ オブジェクトにはトラバーサル インターフェイスがないため、これを ## を通じて追加します。 #Generator
function.interface では、for...of
を使用して##promise、Generator、async/await for をトラバースできます。比較:
ジェネレーターは非同期操作用に設計されていません。他の機能 (オブジェクト反復、制御) もあります出力、Interator インターフェイスのデプロイ...) Promise のコード作成は Generator や async よりも複雑で、可読性もわずかに劣ります Generator と async は、非同期状況を処理するために Promise オブジェクトと組み合わせる必要があります
Async は本質的にジェネレーター構文のシュガーであり、ジェネレーター関数を自動的に実行するのと同等です。Async はより簡潔に使用できます。同期フォームで非同期コードを記述することは、非同期プログラミングの究極のソリューションです。10、js构造函数的静态成员和实例成员
js的构造函数(在别的后台语言上叫做类)上可以添加一些成员,可以在构造函数内部的this上添加,可以在构造函数本身上添加,通过这两种方式添加的成员,就分别称为实例成员和静态成员
实例成员:构造函数中this上添加的成员 静态成员:构造函数本身上添加的成员
实例成员,只能由实例化的对象来访问 静态成员,只能由构造函数本身来访问 实例化对象的proto指向构造函数的prototype属性指向的对象,实例化的对象可以访问到它后者身上的成员
构造函数生成实例的执行过程:使用面向对象编程时,new关键字做了什么?
新建了一个Object对象
修改构造函数this的指向,是其指向新建的Object对象,并且执行构造函数
为Object对象添加了一个proto属性,是其指向构造函数的prototype属性
将这个Object对象返回出去
11、set和map数据结构有哪些常用的属性和方法?
set数据的特点是数据是唯一的
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数据结构
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属性的对象。
将类数组对象转换为真正数组:
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']]ログイン後にコピー那么,如果将上面代码中
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面试题
核心原理部分
mvc mvvm和mvp的区别?
MVVM 就是 Model-View-ViewModel 的缩写,MVVM 将视图和业务逻辑分开。
View:视图层,Model 数据模型,而 ViewModel 是把两者建立通信的桥梁。
MVVM フレームワークでは、View と Model の間に直接の接続はありませんが、ViewModel を通じて相互作用します。 View と ViewModel の間、および Model と ViewModel の間の対話は双方向であるため、ビュー データの変更はモデルに同期され、モデル データの変更はすぐにビューに反映されます。両者はリアルタイムにアップデートされ、影響し合っていると言えるでしょう。 ViewModel は、双方向のデータ バインディングを通じて View 層と Model 層を接続し、View と Model 間の同期は完全に自動です。 したがって、開発者はビジネス ロジックに集中するだけでよく、手動で操作する必要はありません。これらはすべて MVVM,
によって一元管理されており、全体として、MVVM は MVC よりもはるかに単純です。ビジネスとインターフェースの依存関係だけでなく、データの問題も解決します。 頻繁な更新の問題を解決するために、DOM 要素を操作するためにセレクターを使用する必要がなくなりました。 MVVM では、View は Model の存在を知らず、Model と ViewModel は View を観察できないため、この低結合モードによりコードの再利用性が向上します。
[メリット]
データソースとビューは双方向のバインディングを実装しており、データの整合性が非常に良く実現されており、MVP層に比べて結合度が低く、ビューモデル層を共有することが可能です。複数のビューレイヤーによって。
【デメリット】
dataBindingを使用するため、大量のメモリオーバーヘッドが追加され、プログラムのコンパイル時間が増加し、プロジェクトが大きくなるほどメモリオーバーヘッドも大きくなります。データ バインディングによりバグのデバッグが困難になります。 インターフェースに異常が見られる場合は、View コードにバグがあるか、Model コードに問題がある可能性があります。
MVC の正式名は Model View Controller です。 - ビュー - コントローラーの略語、ソフトウェア設計パラダイム
- モデル: アプリケーション データ ロジックを処理するために使用されるアプリケーションの一部です。通常、モデル オブジェクトはデータベース内のデータへのアクセスを担当します。
- ビュー: データの表示を処理するアプリケーションの一部です。通常、ビューはモデル データに基づいて作成されます
- コントローラー: ユーザー操作を処理するアプリケーションの一部です。通常、コントローラーはビューからのデータの読み取り、ユーザー入力の制御、モデルへのデータの送信を担当します。また、View[利点]
低結合を使用してモデル データを表示することもできます。メンテナンスにより、分業、コラボレーション、高い再利用性が促進されます。
[短所]
プロジェクト アーキテクチャが複雑になり、開発者に高い要求が課せられます。
MVP MVP は、クラシック モデル MVC Evolved と基本的な考え方は似ています。コントローラー/プレゼンターはロジック処理を担当し、モデルはデータを提供し、ビューは表示を担当します。MVP では、ビューはモデルを直接使用しません。それらの間の通信はプレゼンター (コントローラー)
#Vue の基礎となる実装原則
##vue.js は、パブリッシャー/サブスクライバー モデルの方法と組み合わせたデータ ハイジャックを使用します。 、Object.defineProperty() を介して各プロパティのセッターとゲッターをハイジャックし、データが変更されたときにサブスクライバーにメッセージをパブリッシュします。Vue テンプレートのコンパイル原則にバインドされたコールバックをトリガーします。対応するリスニング コールバックをトリガーする Vue は典型的な MVVM フレームワークであり、モデル (Model) は単なる通常の JavaScript オブジェクトを変更すると、ビューが自動的に更新されます。この設計により、状態管理が非常にシンプルかつ直感的になります
Observer (データ リスナー): Observer の中心となるのは、Object.defineProprtty() を通じてデータの変更を監視することです。この関数内でセッターを定義できます。データが変更されるたびに、セッターがトリガーされます。この時点で、オブザーバーは、ウォッチャーであるサブスクライバーに通知します。
Compile (命令パーサー): Compile が行う主な処理は、テンプレート命令を解析し、テンプレート内の変数を置き換えることです。テンプレートにデータを含めます。次に、レンダリング ページ ビューを初期化し、更新関数を各命令に対応するノードにバインドし、識別データのサブスクライバを追加します。データが変更されたら、通知を受信してビューを更新します
Watcher (Subscribe Author): Observer と Compile の間の通信のブリッジとして、Watcher サブスクライバは主に次のことを行います:
インスタンス化時にプロパティ サブスクライバ (dep) に移動します。自分自身を追加します。
- update() メソッドが必要です
- dep.notice() によって通知されたときに独自の更新を呼び出すことができますの属性変更 () メソッドを呼び出し、Compile
vue のテンプレート テンプレートは、ブラウザーの標準に属しておらず、正しい HTML 構文ではないため、ブラウザーで解析およびレンダリングできません。そのため、テンプレートは次のことを行う必要があります。 JavaScript 関数に変換されるため、ブラウザーはこの関数を実行し、対応する HTML 要素をレンダリングしてビューを実行できるようになります。この変換プロセスはテンプレートのコンパイルと呼ばれます。Vue のコンパイル プロセスは、テンプレートをレンダリング関数に変換することです。プロセスは次の 3 つのステップに分かれています。
最初のステップは、テンプレート文字列を要素 AST に変換することです ( parser)
2 番目のステップは、主に仮想 DOM レンダリングの最適化 (オプティマイザー) に使用される AST を静的にマークすることです
3 番目のステップは、要素 AST を使用してレンダリング関数コードを生成することです文字列 (コードビルダー)
vue虚拟dom,diff算法
虚拟 DOM,其实就是用对象的方式取代真实的 DOM 操作,把真实的 DOM 操作放在内存当中,在内存中的对象里做模拟操作。当页面打开时浏览器会解析 HTML 元素,构建一颗 DOM 树,将状态全部保存起来,在内存当中模拟我们真实的 DOM 操作,操作完后又会生成一颗 dom 树,两颗 DOM 树进行比较,根据 diff 算法比较两颗 DOM 树不同的地方,只渲染一次不同的地方。
(个人理解)虚拟dom他不并不是真实的 dom ,是根据模板生成一个js对象(使用createElement,方法),根据这个js对象再去生成真实的dom,对复杂的文档DOM结构,提供一种方便的工具,进行最小化的DOM操作 ,是可以快速的渲染和高效的更新元素,提高浏览器的性能,
例如,一个 ul 标签下很多个 li 标签,其中只有一个 li 有变化,这种情况下如果使用新的 ul 去替代旧的 ul,因为这些不必要的 DOM 操作而造成了性能上的浪费,但是如果直接使用虚拟节点覆盖旧节点的话,减少了很多的不必要的 DOM 操作。
我们在渲染页面的时候 会对新的虚拟dom和旧的虚拟dom进行对比 只渲染不同的地方,而不再是像之前只要发生变化,全部的真实dom都要重新渲染,所以提高了渲染的效率。
缺点:首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢
diff算法
diff 算法是一种通过同层的树节点进行比较的高效算法,比较方式:diff整体策略为:深度优先,同层比较
diff算法 当data发生改变 会根据新的数据生成一个新的虚拟dom ,新的虚拟dom和旧的虚拟dom进行对比,这个对比的过程就是diff算法,会找到不同地方,只去渲染不同的地方,总的来说就是减少DOM,重绘和回流。
为什么要用虚拟DOM来描述真实的DOM呢?
创建真实DOM成本比较高,如果用 js对象来描述一个dom节点,成本比较低,另外我们在频繁操作dom是一种比较大的开销。所以建议用虚拟dom来描述真实dom。
响应式原理
vue的响应式原理? 什么是响应式,“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。 Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。 Object.defineProperty怎么用, 三个参数?,有什么作用啊? Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。 Object.defineProperty(obj, prop, {}) obj:需要定义属性的对象 prop:需要定义的属性 {}:要定义或修改的属性描述符。 value: "18", // 设置默认值得 enumerable: true, //这一句控制属性可以枚举 enumerable 改为true 就可以参与遍历了 默认值false writable: true, // 控制属性可以被修改 默认值false configurable: true, // 控制属性可以被删除 默认值false get // 当有人读取 prop 的时候 get函数就会调用,并且返回就是 sss 的值 set // 当有人修改 prop 的时候 set函数就会调用, 有个参数这个参数就是修改后的值 Object.defineProperty 能定义symbol类型吗? 在ES6中,由于 Symbol类型的特殊性,用Symbol类型的值来做对象的key与常规的定义或修改不同,而Object.defineProperty 是定 义key为Symbol的属性的方法之一。 vue2和vue3的响应式原理都有什么区别呢? vue2 用的是 Object.defindProperty 但是vue3用的是Proxy Object.defindProperty虽然能够实现双向绑定了,但是还是有缺点,只能对对象的属性进行数据劫持,所以会深度遍历整个对象,不管层级有多深,只要数组中嵌套有对象,就能监听到对象的数据变化无法监听到数组的变化,Proxy就没有这个问题,可以监听整个对象的数据变化,所以用vue3.0会用Proxy代替definedProperty。 上面就是一个典型的例子,当我们点击按钮想要根据数组 arr 的下标改变其元素的时候,你会发现 data 中的数据改变了,但是页面中的数据并没有改变。 我会用 this.$set( target, key, value ) 来解决 参数: {Object | Array} target {string | number} propertyName/index {any} value 第一参数时指定要修改的数据 (target) 第二个参数就是你要设置数据的下标或者是属性名 第三个参数就是现在要修改的数据 (重新赋的值) 改变/添加 对象属性的时候:this.$set(data 实例,"属性名(添加的属性名)","属性值(添加的属性值)") 改变/添加 数组属性的时候:this.\$set(data 实例,数组下标,"改变后的元素(添加的元素)") 原因 : vue在创建实例的时候把data深度遍历所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。让 Vue 追踪依赖,在属性被访问和修改时通知变化。所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。 为什么要用 this.$set 呢? this.$set是干什么的? 当你发现你给对象加了一个属性,在控制台能打印出来,但是却没有更新到视图上时,也许这个时候就需要用到this.$set()这个方法了,简单来说this.$set的功能就是解决这个问题的啦。官方解释:向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 (比如 this.myObject.newProperty = 'hi'),你会发现vue官网是vue.set,vue.set的用法 那 Vue.set 和 this.$set 有什么区别 ? Vue.set( ) 是将 set 函数绑定在 Vue 构造函数上,this.$set() 是将 set 函数绑定在 Vue原型上。ログイン後にコピーvue双向数据绑定原理?
是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调从而达到数据和视图同步。
主要分为四部分
1、 observer 主要是负责对Vue数据进行递归遍历,使其数据拥有get和set方法,当有数据给某个对象值赋值,就触发 setter 就监听到数据的变化了。( 如有变动可拿到最新值并通知订阅者 )
2、compile 指令解析器负责绑定数据和指令解析。 将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数。一旦数据有变动,收到通知,更新视图
3、 订阅者 watcher : Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是 负责数据监听,当数据发生改变,能调用自身的update()方法,并触发Compile中绑定的更新函数
4、实现一个订阅器 dep: 采用发布者订阅者模式,用来收集订阅者的 watcher,对监听器 observer 和订阅者 watcher 进行统一管理
vue3的Proxy 相比于 vue2的defineProperty 的优势
在vue3 中
Vue3是通过Object.define.proxy 对对象进行代理,从而实现数据劫持。使用Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法
Vue3.0 摒弃了 Object.defineProperty,改为基于 Proxy 的观察者机制探索。
首先说一下 Object.defineProperty 的缺点:
① Object.defineProperty 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。 this.$set()解决
② Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue2.X 里,是通过递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象才是更好的选择。
而要取代它的 Proxy 有以下两个优点
可以劫持整个对象,并返回一个新对象。有多种劫持操作(13 种)
补充:
Proxy 用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。 mdn
Proxy 是 ES6 新增的一个属性,翻译过来的意思就是代理,用在这里表示由它来“代理”某些操作。Proxy 让我们能够以简洁易懂的方式控制外部对象的访问,其功能非常类似于设计模式中的代理模式。
1、vue 中数组中的某个对象的属性发生变化,视图不更新如何解决?
Object.defineProperty 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。 this.$set()解决
问题原因:因为 vue 的检查机制在进行视图更新时无法监测 数组中的对象的某个属性值的变化。解决方案如下
方案一:利用 this.set(this.obj,key,val)
例:this.set(this.obj,‘k1’,‘v1’)
方案二:就利用 Object.assign({},this.obj)创建新对象
如果是数组就 Object.assign([],this.obj)
如果是对象就 Object.assign({},this.obj)。vue.js的两个核心是什么
灵活的组件应用,高效的数据绑定
渐进式框架的理解,vue数据驱动的理解
渐进式代表的含义是:主张最少——它是一个轻量级框架,只做了自己该做的事,没有做不该做的事
每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强势程度会影响在业务开发中的使用方式。
这里的vue数据驱动的是视图,也就是DOM元素,指的是让DOM的内容随着数据的改变而改变框架的理解
Vue的SSR是什么?有什么好处?
SSR全称
Server Side Render
有利于SEO:由于是在服务端,将数据填充进HTML之后再推送到浏览器,所以有利于SEO的爬取
首屏渲染快
SSR的缺点:
开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子;
当需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境;
更多的服务端负载。
vue3.0 与 vue2.0 的区别
1.性能提升
更小巧,更快速;支持摇树优化。支持 Fragments (支持多个根节点)和跨组件渲染;支持自定义渲染器。
2.API 变动
Vue2使用 选项类型API(Options API) 对比Vue3 合成型API(Composition API)
optionsApi 使用传统api中,新增一个需求,要在data,methods,computed中修改
compositionApi 我们可以更加优雅的组织我们的代码,函数,让我们的代码更加有序的组合在一起
3.重写虚拟 DOM (Virtual DOM Rewrite)
随着虚拟 DOM 重写,减少 运行时(runtime)开销。重写将包括更有效的代码来创建虚拟节点。
vue3 没有了过滤器
双向数据绑定 从 Object.defineProperty() 变成了 proxy,通过下标修改数组变化了视图数据没发生变化 this.$set() vue3不需要
双向数据绑定原理发生了改变,使用proxy替换Object.defineProerty,使用Proxy的优势:
可直接监听数组类型的数据变
监听的目标为对象本身,不需要像Object.defineProperty一样遍历每个属性,有一定的性能提升
可直接实现对象属性的新增/删除
setup 函数
3.0新加入了TypeScript以及PWA支持
默认使用懒加载
可以不用加上key
vue3 的watch监听可以进行终止监听
生命周期有了一定的区别 Vue2--------------vue3
beforeCreate -> setup() 开始创建组件之前,创建的是data和method created -> setup() beforeMount -> onBeforeMount 组件挂载到节点上之前执行的函数。 mounted -> onMounted 组件挂载完成后执行的函数 beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。 updated -> onUpdated 组件更新完成之后执行的函数。 beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。 destroyed -> onUnmounted 组件卸载之前执行的函数。 activated -> onActivated 组件卸载完成后执行的函数 deactivated -> onDeactivatedログイン後にコピーvue 与 react的区别
相同点 :
都是使用了虚拟dom
组件化开发
父子之间通信单项数据流
都支持服务端渲染
不同点:
reacct 的jsx vue的是 template
数据变化,react 手动 setState vue自动响应式处理 proxy object.DefineProperty
react 单向数据流 ,vue双向数据流
react 的 redux mobx vue 的vuex 。pinia
vue生命周期(11个进行扩展延伸)
声明周期那几个?每一个生命周期的特点,可以做什么。
beforeCreate() 创建前,这个时候data中的数据,还未定义,所以不能使用 created()创建后 最早开始使用 data和methods中数据的钩子函数 beforeMount()挂载前 指令已经解析完毕内存中已经生成dom树,但是尚未挂载到页面中去,此时页面还是旧的。 mounted()挂载后 dom已经渲染完毕,此时页面和内存中都是最新的数据,最早可以操作DOM元素钩子函数 beforeUpdate()更新前 当视图层的数据发生改变会执行这个钩子 内存更新,但是DOM节点还未更新,数据没有与页面同步 updated()更新后 数据更新完成以后触发的方法,DOM节点已经更新 beforeDestroy()即将销毁 data和methods中的数据此时还是可以使用的,可以做一些释放内存的操作 destroyed()销毁完毕 组件已经全部销毁,Vue实例已经被销毁,Vue中的任何数据都不可用 其他三个: activated 被 keep-alive 缓存的组件激活时调用。 deactivated 被 keep-alive 缓存的组件停用时调用。 errorCaptured 2.5.0+ 新增当捕获一个来自子孙组件的错误时被调用 Vue3.0中的生命周期做了一些改动: beforeCreate -> setup() 开始创建组件之前,创建的是data和method created -> setup() beforeMount -> onBeforeMount 组件挂载到节点上之前执行的函数。 mounted -> onMounted 组件挂载完成后执行的函数 beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。 Update - > onUpdated组件更新完成之后执行的函数。 beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。 destroyed -> onUnmounted 组件卸载之前执行的函数。 - vue的实例加载完成是在哪个声明周期完成呢 beforeCreate - vue的dom挂载完成是在哪个声命周期里呢 mounted 1、created mounted 的区别? created 模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。 mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。 2、怎么在created里面操作dom? this.$nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。 可以根据打印的顺序看到,在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作并无作用,而在created()里使用this.$nextTick()可以等待dom生成以后再来获取dom对象,而通过this.$nextTick()获取到的值为dom更新之后的值 setTimeout(() => { console.log(this.$refs.button); }); 3、那 setTimeout this.$nextTick 什么区别呢? setTimeout 将同步转换为异步 this.$nextTick this.$nextTick 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它, 4、this.$nextTick()是宏任务还是微任务啊? 优先是Promise.then方法,是个微任务,这样可以避免多一次队列,进而少一次UI渲染,节省性能 5、a页面跳转到b页面周期执行 页面a----beforeCreate undefined 页面a----created 1 页面a----beforeMount 1 页面a----mounted 1 页面b----beforeCreate undefined 页面b----created 1 页面b----beforeMount 1 页面a----beforeDestroy 1 页面a----destroyed 1 页面b----mounted 1 6、组件 和 页面周期 的执行顺序 - 页面beforeCreate undefined - 页面created 1 - 页面beforeMount 1 - 组件beforeCreate undefined - 组件created 5555 - 组件beforeMount 5555 - 组件mounted 5555 - 页面mounted 1 7、父子组件生命周期执行顺序 加载渲染过程 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted 代码更新过程 父beforeUpdate->子beforeUpdate->子updated->父updated 代码销毁过程 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed 代码常用钩子简易版 父create->子created->子mounted->父mounted 8、补充单一组件钩子执行顺序 activated, deactivated 是组件keep-alive时独有的钩子 beforeCreate created beforeMount mounted beforeUpdate updated activated deactivated beforeDestroy destroyed errorCaptured watch 仅仅是数据发生改变的时候会侦听到; 只是会检测到你写在watch里的那些属性,没写的就不会触发。 updated 执行到它的时候时候是数据发生变化且界面更新完毕; 不能监听到路由数据(例如网址中的参数); 所有的数据发生变化都会调用(消耗性能); 每次触发的代码都是同一个 computed 1、监控自己定义的变量,不用再data里面声明,函数名就是变量名 2、适合多个变量或对象进行处理后返回一个值(结果)。若这多个变量发生只要有一个发生变化,结果都会变化。 3、计算的结果具有缓存,依赖响应式属性变化,响应式属性没有变化,直接从缓存中读取结果。 4、在内部函数调用的时候不用加()。 5、必须用return返回 6、不要在computed 中对data中的数据进行赋值操作,这会形成一个死循环。 methods不会被缓存:方法每次都会去重新计算结果。methods 方法表示一个具体的操作,主要书写业务逻辑; 使用 methods 方法编写的逻辑运算,在调用时 add() 一定要加“()”,methods 里面写的多位方法,调用方法一定要有()。methods方法页面刚加载时调用一次,以后只有被调用的时候才会被调用。我们在长度框和宽度框的值输入完以后,点击“+” methods 方法调用一次。这里很明显我们采用 methods 会更节省资源。 使用场景? watch: 1、watch 函数是不需要调用的。 2、重点在于监控,监控数据发生变化的时候,执行回调函数操作。 3、当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch 4、函数名就是你要监听的数据名字 5、监控一些input框值的特殊处理,适合一个数据影响多个数据。 6、数据变化时,执行一些异步操作,或开销比较大的操作 computed: 在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式 一个需要的结果受多个数据影响的时候,比如购物车结算金额(受到很多处的价格结算)。 操作某个属性,执行一些复杂的逻辑,并在多处使用这个结果。 内部函数中多处要使用到这个结果的。 1、监控自己定义的变量,不用再data里面声明,函数名就是变量名 2、适合多个变量或对象进行处理后返回一个值(结果)。若这多个变量发生只要有一个发生变化,结果都会变化。 3、计算的结果具有缓存,依赖响应式属性变化,响应式属性没有变化,直接从缓存中读取结果。 4、在内部函数调用的时候不用加()。 5、必须用return返回 6、不要在computed 中对data中的数据进行赋值操作,这会形成一个死循环。ログイン後にコピー一般在哪个生命周期请求异步数据
可以啊钩子函数中的
created
、beforeMount
、mounted
中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。在created中最好
能更快获取到服务端数据,减少页面加载时间,用户体验更好;
SSR不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性。
mounted 在请求完数据之后需要对 dom 进行操作的时候可以用到
vue中 methods,computed, watch 的区别
computed 是vue中的计算属性,具有缓存性,当他的依赖于值,发生改变的时候才会重新调用
methods 是没有缓存的,只要调用,就会执行,一般结合事件来使用
watch 没有缓存性 监听data中的属性 属性值只要发生变化就会执行 可以利用他的特性做一些异步的操作
created和mounted区别?
created:dom渲染前调用,即通常初始化某些属性值
mounted:在dom渲染后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作
生命周期钩子是如何实现的
Vue 的生命周期钩子核心实现是利用发布订阅模式先把用户传入的的生命周期钩子订阅好(内部采用数组的方式存储)然后在创建组件实例的过程中会一次执行对应的钩子方法(发布)
vuex常问的考点
Vuex严格模式
开启严格模式,仅需在创建 store 的时候传入 strict: true: const store = new Vuex.Store({ // ... strict: true }) 在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。ログイン後にコピー开发环境与发布环境
不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
类似于插件,我们可以让构建工具来处理这种情况:
const store = createStore({ // ... strict: process.env.NODE_ENV !== 'production' })ログイン後にコピーvuex是什么, state,getters,mutations,actions,modules的用途和用法
vuex是一个状态管理工具,所谓状态的是就是数据,采用集中式存储管所有组件的状态,是为了结局中大型项目一个数据共享的问题。vuex 他可以将数据保存到本地,数据是响应式的,能够保持数据页面的共享,提高开发效率。
好处:
能够在 vuex 中集中管理共享的数据,易于开发和后期维护 可以做状态管理、采用localstorage保存信息、数据一直存储在用户的客户端中 存储在 vuex 中的数据都是响应式的,能够实时保持数据与页面的同步,能够高效地实现组件之间的数据共享,提高开发 效率
vuex核心:
state:vuex的基本数据,数据源存放地,用于定义共享的数据。
geeter:从基本数据派生的数据,相当于state的计算属性
mutation:提交更新数据的方法,唯一 一个可以操作state 中数据的方法,必须是同步的,第一个参数是state,第二个参数是cmmi传过来的数据
action:action是用来做异步操作的,一般用来发请求,在 action 中写入函数,然后在页面中用dispatch调用,然后在 action 中通过commit 去调用 mutation 通过 mutation 去操作state。
modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理
详述Vuex运行机制
运行机制:Vuex提供数据(state),来驱动视图(这里指的是Vue组件),视图通过Dispatch派发Action,在Action中可以进一步做一些异步的操作(例如通过ajax请求后端的接口数据),然后通过Commit提交给Mutations,由Mutations去最终更改state。那么为什么要经过Mutations呢?这是因为我们要在Vue调试工具(Devtools)中记录数据的变化,这样可以通过插件去进行进一步的调试。所以说Mutations中只能是纯同步的操作,如果是有异步操作,那么就需要在Actions中进行处理。如果说没有异步操作,那么可以直接由组件进行Commit操作Mutations。
高级用法辅助函数(语法糖)
mapState,mapActions,mapMutations,mapGetters
辅助函数可以把vuex中的数据和方法映射到vue组件中。达到简化操作的目的
如何使用:
Import { mapActions, mapGetters, mapMutations, mapState } from 'vuex' computed(){ ...mapState(['数据名字'])}ログイン後にコピーVuex 页面刷新数据丢失怎么解决
需要做 vuex 数据持久化 一般使用本地存储的方案来保存数据 可以自己设计存储方案 也可以使用第三方插件
推荐使用 vuex-persist 插件,它就是为 Vuex 持久化存储而生的一个插件。不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者 localStorage 中
Vuex 为什么要分模块并且加命名空间
模块:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。方便管理
vue的路由
vue-router(路由原理?路由守卫?)
由于Vue在开发时对路由支持的不足,于是官方补充了vue-router插件。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中,则是路径之间的切换,实际上就是组件的切换。路由就是SPA(单页应用)的路径管理器。再通俗的说,vue-router就是我们WebApp的链接路径管理系统。
原理 一般源码中,都会用到 window.history 和 location.hash 原理:通过改变浏览器地址URL,在不重新请求页面的情况下,更新页面视图,通过BOM中的location对象,其中对象中的location.hash储存的是路由的地址、可以赋值改变其URL的地址。而这会触发hashchange事件,而通过window.addEventListener监听hash值然后去匹配对应的路由、从而渲染页面的组件 1.一种是# hash,在地址中加入#以欺骗浏览器,地址的改变是由于正在进行页内导航 2.一种是h5的history,使用URL的Hash来模拟一个完整的URL
路由有两种模式 hash和history模式 默认是hash
vue-router的实现原理(核心):更新视图但不重新请求页面。
1、hash ——即地址栏 URL 中的#符号,它的特点在 于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影 响,因此改变 hash 不会重新加载页面。
2、history ——利用了 HTML5 History api 在浏览器中没有# 有浏览器兼容问题
3、history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,否则返回 404 错误。
一:全局的守卫
无论访问哪一个路径,都会触发全局的钩子函数,位置是调用router的方法 router/index.js
router.beforeEach 全局前置守卫 进入路由之前
router.beforeResolve 全局解析守卫,在beforeRouteEnter调用之后调用
同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用
router.afterEach 全局后置钩子 进入路由之后
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受
next
函数也不会改变导航本身:二:组件级路由守卫 放在要守卫的组件里,跟data和methods同级
beforeRouteEnter 进入路由前,此时实例还没创建,无法获取到zhis
beforeRouteUpdate (2.2) 路由复用同一个组件时
beforeRouteLeave 离开当前路由,此时可以用来保存数据,或数据初始化,或关闭定时器等等
//在组件内部进行配置,这里的函数用法也是和beforeEach一毛一样 const Foo = { template: `...`, beforeRouteEnter (to, from, next) { // 在渲染该组件的对应路由被 confirm 前调用 // 不!能!获取组件实例 `this` // 因为当守卫执行前,组件实例还没被创建 }, beforeRouteUpdate (to, from, next) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候, // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 可以访问组件实例 `this` }, beforeRouteLeave (to, from, next) { // 导航离开该组件的对应路由时调用 // 可以访问组件实例 `this` } }ログイン後にコピー三:单个路由规则独享的守卫 写在路由配置中,只有访问到这个路径,才能触发钩子函数
beforeEnter:(to,from,next)=>{ alert("欢迎来到孙志豪的界面") next() }ログイン後にコピー参数
to: Route
: 即将要进入的目标 路由对象
from: Route
: 当前导航正要离开的路由对象
next: Function
: 一定要调用该方法来 resolve 这个钩子。执行效果依赖next
方法的调用参数。重定向用哪个属性?
redirect:”/路径”
vue路由的跳转方式有几种
1、
2、this.$router.push()跳转到指定的url,并在history中添加记录,点击回退返回到上一个页面 3、this.$router.replace()跳转到指定的url,但是history中不会添加记录,点击回退到上上个页面
4、this.$touter.go(n)向前或者后跳转n个页面,n可以是正数也可以是负数
router.push、router.replace、router.go、router.back的区别?
router.push
:跳转,并向history栈中加一个记录,可以后退到上一个页面
router.replace
:跳转,不会向history栈中加一个记录,不可以后退到上一个页面
router.go
:传正数向前跳转,传负数向后跳转
router.back
返回到上一级页面vue 路由传参数如何实现、query 和 params
主要通过 query 和 params 来实现
(1) query可以使用name和path而params只能使用name
(2) 使用params传参刷新后不会保存,而query传参刷新后可以保存
(3) Params在地址栏中不会显示,query会显示
(4) Params可以和动态路由一起使用,query不可以
(5)to=”/goods?id=1001”this.然后在接收的页面通过 $route.query.id 来接收
路由对象route和router的区别
route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。
router 是“路由实例对象”,包括了路由的跳转方法(push、go),钩子函数等。
vue-router 路由钩子函数是什么 执行顺序是什么执行顺序
1、打开页面的任意一个页面,没有发生导航切换。
全局前置守卫beforeEach (路由器实例内的前置守卫)
路由独享守卫beforeEnter(激活的路由)
组件内守卫beforeRouteEnter(渲染的组件)
全局解析守卫beforeResolve(路由器实例内的解析守卫)
全局后置钩子afterEach(路由器实例内的后置钩子)
2、如果是有导航切换的(从一个组件切换到另外一个组件)
组件内守卫beforeRouteLeave(即将离开的组件)
全局前置守卫beforeEach (路由器实例内的前置守卫)
组件内守卫beforeRouteEnter(渲染的组件)
全局解析守卫beforeResolve(路由器实例内的解析守卫)
全局后置钩子afterEach(路由器实例内的后置钩子)
完整的导航解析流程
导航被触发。
在失活的组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
动态路由:
动态路由是指路由器能够自动的建立自己的路由表,能够根据实际情况的变化实时地进行调整。用开头,后面跟的值是不确定的。这个值是我们要传递的参数 动态路由匹配本质上就是通过url进行传参
比如在写一个商品详情页面的时候,我们的页面结构都一样,只是渲染的数据不同而已,这时候就可以根据商品的不同id去设置动态路由,只需要写一个组件,就可以把每个商品的商品详情映射到同一个组件上去。 { path: '/Jqxq/:id', // 路由配置拼接 name: 'Jqxq', component: Jqxq } 跳转 this.$router.push('/Jqxq/'+ item.id) 接收 : this.$route.params.idログイン後にコピー嵌套路由:
vue项目中,界面通常由多个嵌套的组件构成, 必须先清楚这样一件事,一个
对应展示的就是一个组件 因此实现嵌套路由有两个要点: 路由对象中定义子路由 用children实现嵌套路由 组件内 的使用. 路由配置:
export default new Router({ mode: 'history', //路由模式,取值为history与hash base: '/', //打包路径,默认为/,可以修改 routes: [ { path: string, //路径 ccomponent: Component; //页面组件 name: string; // 命名路由-路由名称 components: { [name: string]: Component }; // 命名视图组件 redirect: string | Location | Function; // 重定向 props: boolean | string | Function; // 路由组件传递参数 alias: string | Array<string>; // 路由别名 children: Array<RouteConfig>; // 嵌套子路由 // 路由单独钩子 beforeEnter?: (to: Route, from: Route, next: Function) => void; meta: any; // 自定义标签属性,比如:是否需要登录 icon: any; // 图标 // 2.6.0+ caseSensitive: boolean; // 匹配规则是否大小写敏感?(默认值:false) pathToRegexpOptions: Object; // 编译正则的选项 } ] })ログイン後にコピー怎么定义 vue-router 的动态路由? 怎么获取传过来的值
在 router 目录下的 index.js 文件中,对 path 属性加上 /:id,使用 route 对象的 params.id 获取
Vue-router共有几种模式?默认是那种?
有两种模式 hash和history模式 默认是hash
1、hash ——即地址栏 URL 中的#符号,它的特点在 于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影 响,因此改变 hash 不会重新加载页面。
2、history ——利用了 HTML5 History api 在浏览器中没有# 有浏览器兼容问题
history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 地址后加上/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。
路由懒加载
使用原因:在单页应用中,如果没有应用懒加载,运用 webpack 打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,延时过长,不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时 原理:vue 异步组件技术:异步加载,vue-router 配置路由 , 使用 vue 的异步组件技术 , 实现按需加载。
{ path: '/home', component: () => import('@/views/home/home.vue') } // 懒加载ログイン後にコピー能说下 vue-router 中常用的路由模式实现原理吗
hash 模式
location.hash 的值实际就是 URL 中#后面的东西 它的特点在于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
可以为 hash 的改变添加监听事件
window.addEventListener("hashchange", funcRef, false);ログイン後にコピー每一次改变 hash(window.location.hash),都会在浏览器的访问历史中增加一个记录利用 hash 的以上特点,就可以来实现前端路由“更新视图但不重新请求页面”的功能了
特点:兼容性好但是不美观
history 模式
利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。
这两个方法应用于浏览器的历史记录站,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。
指令部分进行扩展
vue常用修饰以及常见指令
修饰符 .stop 阻止事件冒泡 .cpture 设置事件捕获 .self 只有当事件作用在元素本身才会触发 .prevent 阻止默认事件,比如超链接跳转 .once 事件只能触发一次 .native 触发js原生的事件 .number 把文本框的内容转换为数字 .trim 去除文本框左右空格ログイン後にコピー
常见指令
⑴v-bind:给元素绑定属性 ⑵v-on:给元素绑定事件 ⑶v-html:给元素绑定数据,且该指令可以解析 html 标签 ⑷v-text:给元素绑定数据,不解析标签 ⑸v-model:数据双向绑定 ⑹v-for:遍历数组 ⑺v-if:条件渲染指令,动态在 DOM 内添加或删除 DOM 元素 ⑻v-else:条件渲染指令,必须跟 v-if 成对使用 ⑼v-else-if:判断多层条件,必须跟 v-if 成对使用 ⑽v-cloak:解决插值闪烁问题 ⑾v-once:只渲染元素或组件一次 ⑿v-pre:跳过这个元素以及子元素的编译过程,以此来加快整个项目的编译速度 ⒀v-show:条件渲染指令,将不符合条件的数据隐藏(display:none)ログイン後にコピーv-for 与 v-if 的优先级
v-for 比 v-if 优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。
vue中key 的作用
“key 值:用于管理可复用的元素。因为 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做使 Vue 变得非常快,但是这样也不总是符合实际需求。 2.2.0+ 的版本里,当在组件中使用 v-for 时,key 是必须的。”
key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点,更高效的对比虚拟DOM中每个节点是否是相同节点,相同就复用,不相同就删除旧的创建新的
key 是使用 index 还是 id 啊
当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key 的作用主要是为了高效的更新虚拟 DOM。
举例子:加入写一个带有复选框的列表
选中第一个节点的复选框,点击删除,vue中是这样操作的,删除后新的数据这时会进行比较,第一个节点的标签一样,值不一样,就会复用原来位置的标签,不会做删除和创建,在第一个节点中是将复选框选中的,当我们看见好像是把第一个删除了,但是点击后去看复选框的时候还是选中在第一个,如果是直接将第一个节点删除了那么复选框就不会选中。
vue 初始化页面闪动问题。
能够解决插值表达式闪烁问题,需要在style中设置样式[v-clock]{display:none}
v-if和v-show的区别及使用场景?
v-if 动态的创建或者销毁元素,为true的时候为显示,为false的时候不显示,要使用v-else必须和v-if紧挨着
v-show 是控制元素的显示或者隐藏,在我们的标签上会看到有display:block,none
v-if 有更高的切换消耗,而 v-show 有更高的初始化渲染消耗,一般推荐频繁切换的时候使用 v-show 更好,当我们的判断分支比较多的时候,和首次渲染的时候 使用v-if
自定义指令,自定义过滤器
出了vue自带的指定以外,我们如果需要对dom进行底层操作的时候这里就用到了自定义指令,分为一下
全局:
vue.directive:{"",{}}
局部:directives:{指令名:{钩子函数}}
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。
参数:
el:指令所绑定的元素
binding:一个对象包含一下,
name:指令名,不包括 v- 前缀。
value:指令的绑定值
补充 : vue3的自定义指令钩子函数?
created - 自定义指令所在组件, 创建后
beforeMount - 就是Vue2.x中的 bind, 自定义指令绑定到 DOM 后调用. 只调用一次, 注意: 只是加入进了DOM, 但是渲染没有完成
mounted - 就是Vue2.x中的 inserted, 自定义指令所在DOM, 插入到父 DOM 后调用, 渲染已完成(最最重要)
beforeUpdate - 自定义指令所在 DOM, 更新之前调用
updated - 就是Vue2.x中的 componentUpdated
beforeUnmount - 销毁前
unmounted - 销毁后
自定义指令原理
在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性
通过 genDirectives 生成指令代码
在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子
当执行指令对应钩子函数时,调用对应指令定义的方法
选项对象和常用api
什么是过滤器?怎么定义全局和局部过滤器
过滤器是对 即将显示的数据做进一步的筛选处理,然后显示,过滤器并没有改变原来的数据,只是在原数据的基础上产生新的数据
全局:
Vue.filter(‘过滤器名’,funciton(val){})ログイン後にコピー局部过滤器,定义在组件内部 filters 属性上.它只能在此组件内部使用.
filters:{过滤器名:funciton(参数){//逻辑代码}}ログイン後にコピー使用: 过滤时间,过滤金钱
什么是 mixin ?
Mixin 使我们能够为 Vue 组件编写可插拔和可重用的功能。 mixin 项目变得复杂的时候,多个组件间有重复的逻辑就会用到mixin
如果希望在多个组件之间重用一组组件选项,例如生命周期 hook、 方法等,则可以将其编写为 mixin,并在组件中简单的引用它。
然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。
在vue.js中mixin和页面执行顺序问题
mixin中的代码先执行,单文件中的后执行。
mixin的beforeCreate > 父beforeCreate > mixin的created > 父created > mixin的beforeMount > 父beforeMount > 子beforeCreate > 子created > 子beforeMount > 子mounted > mixin的mounted >父mounted
nextTick 使用场景和原理
在下次DOM更新循环结束后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。使用场景是:可以在created钩子函数中拿到dom节点
nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法
vue的删除数组和原生删除数组的区别delete
删除数组
delete只是把数组元素的值变成empty/undefined,元素的键不变,数组长度不变。
Vue.delete直接删除数组,改变数组的键值和长度。
删除对象
两者相同,都会把键名(属性/字段)和键值删除。
Vue.extend 作用和原理
官方解释:Vue.extend 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
其实就是一个子类构造器 是 Vue 组件的核心 api 实现思路就是使用原型继承的方法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并 基础用法
<div id="mount-point"></div> // 创建构造器 /* Vue.extend( options ) 参数:{Object} options 用法:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象; data 选项是特例,需要注意: 在 Vue.extend() 中它必须是函数;*/ var Profile = Vue.extend({ template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>', data: function () { return { firstName: 'Walter', lastName: 'White', alias: 'Heisenberg' } } }) // 创建 Profile 实例,并挂载到一个元素上。 new Profile().$mount('#mount-point') // 结果如下: <p>Walter White aka Heisenberg</p> /* 可以看到,extend 创建的是 Vue 构造器,而不是我们平时常写的组件实例,所以不可以通过 new Vue({ components: testExtend }) 来直接使用,需要通过 new Profile().$mount(’#mount-point’) 来挂载到指定的元素上。 */ログイン後にコピー组件模块部分(插槽,单页面,通信)
vue 组件父子,子父,兄弟通信
父传递子如何传递
(1)在父组件的子组件标签上绑定一个属性,挂载要传输的变量 (2)在子组件中通过props来接受数据,props可以是数组也可以是对象,接受的数据可以直接使用 props: ["属性名"] props:{属性名:数据类型}
子传递父如何传递
(1)在父组件的子组件标签上自定义一个事件,然后调用需要的方法 (2)在子组件的方法中通过 this.$emit("事件")来触发在父组件中定义的事件,数据是以参数的形式进行传递的
兄弟组件如何通信
(1)找到min.js文件,给他vue挂载一个 公共的 $bus Vue.prototype.$bus = new Vue() (2)传送数据的一方 用this.$bus.$emit('事件名','传送的数据') (3)在 接收数据的一方用通过 Bus.$on("事件名",(data)=>{data是接受的数据})
vue12种通信方式:
https://blog.csdn.net/qq_54753561/article/details/122281196?spm=1001.2014.3001.5502
prop 验证,和默认值
props:会接收不同的数据类型,常用的数据类型的设置默认值的写法,Number, String, Boolean, Array, Function, Object
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级prop 的更新流动到子组件中,但是反过来则不行。这样防止子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。若果在子组件中直接修改prop传递的值,Vue会发出警告,
方法二:通过 vuex 实现 (要了解)
具体实现:vuex 是一个状态管理工具,主要解决大中型复杂项目的数据共享问题,主要包括 state,actions,mutations,getters 和 modules 5 个要素,主要流程:组件通过 dispatch 到 actions,actions 是异步操作,再 actions中通过 commit 到 mutations,mutations 再通过逻辑操作改变 state,从而同步到组件,更新其数据状态
组件中写 name 选项有什么作用
① 项目使用 keep-alive 时,可搭配组件的 name 进行缓存过滤。
② DOM 做递归组件时需要调用自身 name
③ vue-devtools 调试工具里显示的组件名称是由 vue 中组件 name 决定的
自定义组件
在vue 中的 component 中新建组件,定义好视图层,
人気のチュートリアル詳細>最新のダウンロード詳細>