技能一 写函数判断变量的数据类型
使用JavaScript来判断变量的数据类型。以下是一个示例函数,用于判断变量的数据类型:
function checkVariableType(variable) { return typeof variable; }
这个函数接受一个参数variable,然后使用typeof操作符获取它的数据类型。
下面是一个例子演示如何使用这个函数来检查变量的数据类型:
//
测试示例:
var var1 = 10;
var var2 = "Hello";
var var3 = [1, 2, 3];
var var4 = {a: 1, b: 2};
列如:
console.log(check Variable Type(var1)); // 输出:number console.log(checkVariableType(var2)); // 输出:string console.log(checkVariableType(var3)); // 输出:object console.log(checkVariableType(var4)); // 输出:object
JavaScript中,数组、对象等复杂类型都被归类为object类型。如果您需要更细分的数据类型判断,可以使用其他方法或库来实现。
常见方式:
typeof
一般用于基础数据类型的判断,因为对于 引用类型,typeof返回的总是 object
介绍: 运算符返回一个字符串,表示操作数的类型
代码:
const data1 = 0;
const data2 = '',
const data3 = false;
const data4 = NaN
const data5 = undefinde;
const data6 = null;
const data7 = {};
const data8 = [];
const data9 = fuction(){};
const data10 = Symbol();
console.log(typeof data1) // number
console.log(typeof data2) // string
console.log(typeof data3) // boolean
console.log(typeof data4) // number
console.log(typeof data5) // undefined
console.log(typeof data6) // object
console.log(typeof data7) // object
console.log(typeof data8) // object
console.log(typeof data9) // fucnction
console.log(typeof data10) // symbol
备注: typeof 返回 object typeof NaN 返回 number
instanceof 通过 原型链 来实现的方式
- 理解原型链应该都知道,实例对象有个 ——proto——属性,指向了构造函数的原型。
- 介绍:
- instanceof运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原理上
function Person(name) {
this.name = name;
}
var person = new Person("John");
console.log(person instanceof Person); // 输出:true
console.log(person instanceof Object); // 输出:true
console.log(person instanceof Array); // 输出:false
Person是一个构造函数,用于创建person实例。通过使用instanceof运算符,我们可以检查person是否是Person类的实例。第一个输出是true,因为person确实是由Person类创建的实例。
要注意的是,instanceof还表明一个对象是否是它所在原型链中给定构造函数的实例。因此,person也被判断为Object的实例,因为Object是Person构造函数的原型链上的一个构造函数。
最后一个输出是false,因为person不是Array的实例。
使用instanceof可以帮助您判断对象的类型和层次结构。但是,请注意instanceof只能用于判断对象实例,而不能用于基本数据类型(如字符串、数字等)。
isPrototypeOf 方法用于测试一个对象是否存在于另一个对象的原型链上 介绍: isPrototypeof() 与 instanceof 运算符不同。 在表达式 "object instanceof AFunction "中, object的原型链是针对 AFunction.prototype 进行检查的,而不是针对 AFunction本身。
function Person(name){
this.name = name;
}
var person = new Person("John");
console.log(Person.prototype.isPrototypeOf(person)); // 输出:true
console.log(Object.prototype.isPrototypeOf(person)); // 输出:true
console.log(Array.prototype.isPrototypeOf(person)); // 输出:false
示例中,Person是一个构造函数,用于创建person实例。通过使用isPrototypeOf方法,我们可以检查Person.prototype是否位于person的原型链上。第一个输出结果是true,因为Person.prototype确实是person的原型。
然后,我们使用Object.prototype进行检查。由于Object.prototype是所有JavaScript对象的最顶层原型,它也位于person的原型链上。因此,第二个输出结果同样是true。
最后一个输出结果是false,因为Array.prototype不位于person的原型链上。尽管数组是对象,但person并不是通过Array构造函数创建的实例。
isPrototypeOf方法可以帮助您验证对象之间的原型关系。它检查给定对象的原型链上是否存在目标对象或其派生自目标对象的原型。但请注意,isPrototypeOf只能用于对象实例,而不能用于基本数据类型(如字符串、数字等)。
constructor 实例对象的 constructor属性指向创建实例对象的构造函数
使用 constructor 判断变量数据类型的示例:
var variable = "Hello";
console.log(variable.constructor === String); // true
var number = 42;
console.log(number.constructor === Number); // true
var booleanValue = true;
console.log(booleanValue.constructor === Boolean); // true
var array = [1, 2, 3];
console.log(array.constructor === Array); // true
var object = { name: "John", age: 25 };
示例中,我们通过比较变量的 constructor 属性与相应的构造函数来判断变量的数据类型。如果返回值为 true,则表示变量的数据类型与构造函数匹配。
需要注意的是,对于基本的数据类型(如字符串、数字和布尔值),其 constructor 属性会指向相应的包装对象构造函数(如 String、Number 和 Boolean)。所以在判断基本数据类型时,需要考虑到这一点。
然而,对于复杂的数据类型(如数组和对象),其 constructor 属性指向的仍然是预定义的原始构造函数(例如 Array 和 Object)。
Object.prototype.isPrototypeOf()
使用 Object.prototype.isPrototypeOf() 判断变量数据类型的示例:
var variable = "Hello";
console.log(Object.prototype.isPrototypeOf(variable)); // false
var number = 42;
console.log(Object.prototype.isPrototypeOf(number)); // false
var booleanValue = true;
console.log(Object.prototype.isPrototypeOf(booleanValue)); // false
var array = [1, 2, 3];
console.log(Array.prototype.isPrototypeOf(array)); // true
var object = { name: "John", age: 25 };
console.log(Object.prototype.isPrototypeOf(object)); // true
示例中,我们通过调用 Object.prototype.isPrototypeOf() 并传入要检查的对象来判断该对象的数据类型。如果返回值为 true,则表示对象的类型符合预期。
需要注意的是,Object.prototype.isPrototypeOf() 只能用于判断对象的数据类型,而不能用于基本数据类型(如字符串、数字和布尔值)。对于基本数据类型,你可以使用其他方法,如 typeof 运算符。
Object.prototype.toString.call()(最推荐使用)
使用 Object.prototype.toString.call() 判断变量数据类型的示例:
var variable = "Hello";
console.log(Object.prototype.toString.call(variable)); // [object String]
var number = 42;
console.log(Object.prototype.toString.call(number)); // [object Number]
var booleanValue = true;
console.log(Object.prototype.toString.call(booleanValue)); // [object Boolean]
var array = [1, 2, 3];
console.log(Object.prototype.toString.call(array)); // [object Array]
var object = { name: "John", age: 25 };
console.log(Object.prototype.toString.call(object)); // [object Object]
通过调用 Object.prototype.toString.call() 并传入要检查的变量作为参数,它将返回一个描述该变量类型的字符串,其中包含了 [object Type] 的格式。
需要注意的是,这种方法对于基本数据类型(如字符串、数字和布尔值)也适用。返回的结果会是 [object String]、[object Number] 和 [object Boolean]。
技能二 用reduce统计字符出现频率
reduce-one种 使用对象进行统计
使用 Array.prototype.reduce() 方法来统计字符串中每个字符的出现频率。下面是一个使用 reduce() 统计字符频率的示例代码:
var str = "hello world";
var frequency = str.split("").reduce(function(obj, char) {
if (char in obj) {
obj[char]++;
} else {
obj[char] = 1;
}
return obj;
}, {});
console.log(frequency);
在这个例子中,我们首先使用 split("") 将字符串拆分成字符数组,然后调用 reduce() 方法对该数组进行迭代。在每次迭代中,我们检查当前字符是否已经存在于结果对象 obj 中。如果存在,我们将对应字符的计数加一;如果不存在,我们将该字符添加到 obj 并初始化计数为 1。
最后,reduce() 返回的结果就是一个包含了每个字符及其出现频率的对象。你可以通过打印 frequency 来查看结果。
运行上述示例代码,输出将会是:
{
h: 1,
e: 1,
l: 3,
o: 2,
' ': 1,
w: 1,
r: 1,
d: 1
}
其中,每个字符都作为对象的属性,对应的值表示该字符出现的次数。
reduce-two种 使用 Map 对象进行统计
var str = "hello world";
var frequency = str.split("").reduce(function(map, char) {
map.set(char, (map.get(char) || 0) + 1);
return map;
}, new Map());
console.log(frequency);
使用对象进行统计的方法类似,不同之处在于使用了 Map 对象来存储字符和出现次数的键值对。在回调函数中,使用 map.get(char) 获取字符的当前出现次数,并将出现次数加 1。最后返回更新后的 Map 对象。
运行上述示例代码,输出将会是:
Map(8)
{
'h' => 1,
'e' => 1,
'l' => 3,
'o' => 2,
' ' => 1,
'w' => 1,
'r' => 1,
'd' => 1
}
reduce-three种 使用数组进行统计
var str = "hello world";
var frequency = str.split("").reduce(function(arr, char) {
var item = arr.find(function(item) {
return item[0] === char;
});
if (item) { item[1]++;
} else {
arr.push([char, 1]);
}
return arr;
}, []);
console.log(frequency);
使用数组来存储字符和出现次数的二元组。在回调函数中,使用 arr.find() 方法寻找当前字符是否已经存在于数组中。如果存在,则将对应项的出现次数加 1;如果不存在,则将新的字符和计数项 [char, 1] 添加到数组中。最后返回更新后的数组
运行上述示例代码,输出将会是:
[ ['h', 1],
['e', 1],
['l', 3],
['o', 2],
[' ', 1],
['w', 1],
['r', 1],
['d', 1]
]
技能三 借用原型链补充数组的高阶排序方法 (这个不是很理解)
需要对数组进行高阶排序时,可以借用原型链添加自定义的排序方法。以下是一个示例,演示如何使用原型链补充数组的高阶排序方法:
// 原型链补充数组排序方法
Array.prototype.customSort = function(compareFn) {
return this.sort(compareFn);
};
// 使用自定义排序方法
const numbers = [5, 3, 8, 1, 2, 9, 4, 7, 6];
// 自定义的比较函数
const compareFn = (a, b) => a - b;
// 调用自定义排序方法
const sortedNumbers = numbers.customSort(compareFn);
console.log(sortedNumbers); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
**
在上述示例中,我们通过将自定义排序方法 customSort 添加到 Array.prototype 上,将其扩展为数组的原型链方法。 customSort 方法接受一个比较函数作为参数,并使用 sort 方法进行排序。
然后,我们声明一个名为 numbers 的数组,并定义一个比较函数 compareFn,该函数用于比较数组元素的大小。
最后,我们通过调用 customSort 方法并传入比较函数 compareFn 对 numbers 数组进行排序,将排序结果赋值给 sortedNumbers。
注意:扩展原型链可能会导致一些潜在的问题和命名冲突,因此在实际使用中需要慎重考虑。优选的方式是通过创建独立的函数或工具类来实现高阶排序方法,以避免潜在冲突和不必要的复杂性
技能四 掌握递归的实现原理,能做3个递归的常见题
理解递归的实现原理后,你可以通过解决一些常见的递归问题来进一步巩固你的知识。以下是三个常见的递归问题及其解决方案:
- 阶乘(Factorial):
阶乘是一个常见的递归问题。它定义为一个非负整数 n 的阶乘(记作 n!)等于 n 乘以 n-1 的阶乘。也就是说,n! = n * (n-1)!。当 n 等于 0 或 1 时,阶乘的结果为 1。
以下是使用递归计算阶乘的示例代码:
function factorial(n) {
if (n === 0 || n === 1) {
return 1;
}
return n * factorial(n - 1);
}
console.log(factorial(5)); // 输出: 120
**
- 斐波那契数列(Fibonacci Sequence):
斐波那契数列是一系列的数字,其中每个数字都是前两个数字的和。数列的前两个数字是 0 和 1。
以下是使用递归计算斐波那契数列的示例代码:
function fibonacci(n) {
if (n === 0) {
return 0;
}
if (n === 1) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
console.log(fibonacci(6)); // 输出: 8
**
- 反转字符串(Reverse String):
反转字符串是一个递归问题,可以通过递归地反转字符串的子串来实现。反转一个空字符串或只有一个字符的字符串时,结果还是它本身。
以下是使用递归反转字符串的示例代码:
function reverseString(str) {
if (str === '') {
return '';
}
return reverseString(str.substr(1)) + str.charAt(0);
}
console.log(reverseString('Hello')); // 输出: olleH
**
在递归函数中,我们使用条件语句来检查基本情况(即递归终止条件),并递归地调用函数来处理更小规模的问题,直到达到基本情况。这样,递归函数会回溯,依次解决每个子问题,并最终得到最终结果。
技能五 掌握ref reactive computed define props define emits 的类型注解
Vue 3中,使用TypeScript对ref、reactive、computed、defineProps、defineEmits以及define等函数进行类型注解是很有用的。以下是对这些函数的类型注解示例:
ref:
ref函数用于创建一个响应式的引用类型数据,并返回一个包装后的响应式对象。
import { ref, Ref } from 'vue';
const count: Ref<number> = ref(0);
**
上述示例中,ref(0)创建了一个初始值为0的引用类型数据,并使用Ref<number>类型注解。
reactive:
reactive函数用于将一个普通对象转换为响应式对象。
import { reactive, ReactiveFlags, UnwrapRef } from 'vue';
interface User {
name: string;
age: number;
}
const user: UnwrapRef<User> = reactive({
name: 'John Doe',
age: 25,
});
**
上述示例中,reactive函数将一个普通对象转换成响应式对象,并使用UnwrapRef<User>类型注解。
computed:
computed函数用于创建一个计算属性,并返回一个带有value属性的响应式对象。
import { computed, ComputedRef } from 'vue';
const fullName: ComputedRef<string> = computed(() => {
return user.name + ' ' + user.surname;
});
**
上述示例中,computed函数创建了一个计算属性并计算全名,然后使用ComputedRef<string>类型注解。
defineProps和defineEmits:
defineProps和defineEmits函数用于提供类型安全的属性定义和事件定义。
import { defineProps, defineEmits, SetupContext, ComponentPublicInstance } from 'vue';
interface MyProps {
name: string;
age: number;
}
const props = defineProps<MyProps>();
const emit = defineEmits<{}>();
export default {
props,
setup(props, context: SetupContext<Record<string, any>>) {
// 使用props
console.log(props.name, props.age);
// 使用emit
emit(context, 'eventName');
},
}
**
上述示例中,我们使用 defineProps来定义属性的类型,并使用 defineEmits来定义事件的类型。同时,我们还使用 SetupContext 和 ComponentPublicInstance 提供类型注解以使用 props 和 emit。
define:
define函数用于定义组件的属性或方法,并返回具体的编译时类型。
import { defineComponent, defineProps, ref } from 'vue';
interface MyProps {
name: string;
}
export default defineComponent({
props: defineProps<MyProps>(),
setup(props) {
const count = ref(0);
function increment() {
count.value++;
}
return {
count,
increment,
};
},
});
**
上述示例中,我们使用 defineComponent 来定义组件,使用 defineProps来定义属性的类型,并结合 ref 来创建一个响应式的计数器。
以上是一些类型注解的示例,帮助你在Vue 3中使用TypeScript进行更安全和可靠的开发。请注意,这些示例仅用于演示目的,你可以根据具体的应用程序要求进行相应的修改和扩展。
技能六 掌握interface和type的区别
TypeScript中,interface(接口)和type(类型别名)都用于定义自定义类型,但它们有一些区别:
- 定义方式:
interface:使用interface关键字来定义,可以通过扩展其他接口来创建更复杂的接口结构。type:使用type关键字来定义,可以用来创建复杂的类型别名,包括交叉类型和联合类型。
- 可扩展性:
interface:支持扩展其他接口,可以通过继承其他接口来引入和组合字段。type:不支持扩展其他类型别名,只能通过交叉类型和联合类型来组合现有的类型别名。
- 实例化类型:
interface:可以用于定义类的实例化类型,用于约束类的实例属性和方法。type:不能直接用于定义类的实例化类型,仅适用于定义其他类型的别名。
- 兼容性:
interface:对于类型兼容性检查,支持类型的逐一检查,只有所有字段完全匹配才被认为是兼容的。type:对于类型兼容性检查,宽泛地对待类型,并且允许字段少于目标类型或是多于目标类型。
总的来说,interface适用于定义对象的形状、类的实例属性和方法,以及接口之间的扩展和实现;而type适用于创建复杂的类型别名和进行联合类型或交叉类型的组合。
在实际使用中,你可以根据具体的需求和场景来选择使用interface还是type,有时候它们也可以互相替代使用,取决于个人的偏好和项目的要求。
技能七 掌握字面量和联合类型的使用
字面量和联合类型在TypeScript中常用于为变量或参数指定多个可能的值。
- 字面量类型:
字面量类型允许你明确地指定一个值只能是特定的字面量值之一。可以使用字符串字面量类型、数字字面量类型、布尔字面量类型和枚举字面量类型。
let name: "John"; // 字符串字面量类型
let age: 25 | 30 | 35; // 数字字面量类型
let isLogged: true; // 布尔字面量类型
enum Color {
Red,
Green,
Blue
}
let color: Color.Red | Color.Green; // 枚举字面量类型
**
- 联合类型:
联合类型用于指定一个变量或参数可以接受多个可能的类型。使用|操作符来定义联合类型。
let size: number | string; // 可以是数字或字符串类型
let result: boolean | number; // 可以是布尔或数字类型
function printId(id: string | number) {
console.log(id);
}
printId("123"); // 可以传入字符串
printId(456); // 可以传入数字
**
在使用联合类型时,可以根据需要对每种类型采取不同的操作或逻辑分支,以实现更灵活的编程。
需要注意的是,字面量类型和联合类型可以结合使用,以指定变量或参数可以接受多个字面量值中的某一个。
let status: "success" | "error" | "pending"; // 可以是 success、error 或 pending 字符串之一
function processStatus(status: "success" | "error") {
if (status === "success") {
console.log("操作成功");
} else if (status === "error") {
console.log("操作失败");
}
}
processStatus("success"); // 可以传入 success
processStatus("error"); // 可以传入 error
**
通过使用字面量类型和联合类型,你可以在TypeScript中获得更严格和精确的类型检查,帮助你编写更可靠的代码。
技能八 掌握axios在ts的使用(有代码+能口述)
当使用 TypeScript 来使用 axios 时,你需要先安装 axios 库并导入它,然后使用合适的类型注解来定义请求参数和响应数据的类型。下面是一个使用 axios 发送 GET 请求获取用户数据的示例:
首先,确保已经安装了 axios 和相关的类型声明:
npm install axios @types/axios
**
然后,在你的 TypeScript 文件中导入 axios 和相关类型,可以按照以下方式编写代码:
import axios, { AxiosResponse } from 'axios';
// 定义响应数据类型
interface UserData {
id: number;
name: string;
email: string;
}
// 发送 GET 请求,并使用 AxiosResponse<UserData[]> 指定响应的类型
axios.get<UserData[]>('https://api.example.com/users')
.then((response: AxiosResponse<UserData[]>) => {
const users: UserData[] = response.data;
console.log(users);
})
.catch((error: any) => {
console.error(error);
});
**
以上代码首先导入了 axios 和 AxiosResponse 类型。接着,我们定义了一个名为 UserData 的接口,用于指定响应数据的类型,其中包含 id、name 和 email 字段。
然后,我们使用 axios.get 方法发送 GET 请求,并使用泛型 <UserData[]> 指定响应的数据类型是 UserData[],即一个 UserData 对象数组。这样,在 .then 的回调函数中,我们可以通过 response.data 获取到响应数据,并将其赋值给类型为 UserData[] 的 users 变量进行处理。
最后,我们在 .catch 的回调函数中捕捉异常,输出错误信息。
通过以上代码,你可以在 TypeScript 中使用 axios 发送网络请求,并且获得类型安全的响应数据。你可以根据实际情况进一步添加请求参数等内容,这是一个基本的示例供参考。
技能九 使用mixin技术来复用代码
Mixin 是一种在多个组件之间共享代码的技术,它可以帮助我们实现代码的复用。在 Vue 中,我们可以使用 mixin 来定义一些可复用的选项,并将其混入到一个或多个组件中。下面是使用 mixin 技术来复用代码的示例:
首先,我们定义一个名为 loggerMixin 的 mixin 对象,它包含了一些共享的方法或选项:
import { ComponentOptionsMixin } from 'vue';
const loggerMixin: ComponentOptionsMixin = { created() { this.logMessage('Component created'); }, methods: { logMessage(message: string) { console.log(message); }, }, };
export default loggerMixin; 在上述代码中,loggerMixin 包含了一个 created 钩子函数,会在组件实例被创建时被调用,并且还定义了一个 logMessage 方法,用于打印日志消息。
接下来,我们可以通过 mixins 选项将 loggerMixin 混入到一个或多个组件中:
import { defineComponent, ComponentOptionsMixin } from 'vue'; import loggerMixin from './loggerMixin';
export default defineComponent({ mixins: [loggerMixin], created() { this.logMessage('Hello from mixin'); // 通过 loggerMixin 中的方法调用 }, }); 在上述代码中,我们使用 mixins 选项将 loggerMixin 引入了当前组件,从而实现了代码的复用。通过这种方式,我们可以在组件中共享 loggerMixin 中定义的方法和逻辑。
值得注意的是,如果组件中的选项与混入的选项发生冲突,则组件的选项会优先被使用。如果存在冲突的方法,在使用 mixic 时,可以使用 $options 属性来访问混入的方法:
created() { this.logMessage('Component created'); // 组件自身的方法 this.$options.methods?.logMessage?.('Mixin log message'); // 使用混入的方法 } 通过 mixin 技术,我们可以方便地在 Vue 组件中共享和复用代码,减少了重复编写的工作。但要注意控制 mixin 的使用,避免滥用和导致代码难以维护的情况。
技能十 能清晰准确描述v-model和.sync的区别
v-model 和 .sync 都是在 Vue 中用于实现双向数据绑定的指令,但它们有一些区别:
v-model 是一个语法糖,可以简化双向数据绑定的写法。它能够自动为一个表单元素或组件添加 value 属性和 input 事件监听,并将组件的状态与数据对象进行绑定,实现数据的双向同步。
.sync 是一个修饰符,用于更新父组件中的一个属性,在子组件中进行更改时实现父子组件之间的双向绑定。.sync 修饰符需要添加到一个通过 props 在子组件中接收的属性之前,可以使用 .sync 修饰符来简化对属性的修改。
具体来说,以下是它们的区别:
- 使用方式:
v-model:通常用于表单元素和自定义组件上,用于实现双向数据绑定,通过在组件上绑定v-model指令,可以自动更新组件的值,并将更改的值传递给父组件。.sync:用于子组件更新父组件中的属性。父组件通过向子组件传递一个作为props的属性,并为属性添加.sync修饰符来实现父子组件之间的双向绑定。
- 实现方式:
v-model:自动为表单元素或自定义组件添加value属性和input事件监听,并将组件的状态与数据对象进行双向绑定。.sync:通过在子组件中使用$emit方法触发一个名为update:propertyName的自定义事件来实现更新父组件属性。
- 适用对象:
v-model:通常用于表单元素(如 input、select、textarea 等)或自定义组件上,用于实现表单输入的双向绑定。.sync:通常用于子组件与父组件之间传递数据,通过.sync修饰符,使得子组件可以修改父组件传递的属性值。
需要注意的是,.sync 只能修改父组件的属性值,而不能直接在子组件中修改传入的 prop。如果想在子组件内部修改 prop 的值,可以通过创建一个局部的 data 属性来实现,然后使用 .sync 修饰符将其与父组件的 prop 进行双向绑定。
总之,v-model 和 .sync 都是用于实现数据的双向绑定,但 v-model 更适用于表单输入元素或自定义组件的双向绑定,而 .sync 则更适用于父子组件之间的属性传递和同步更新。
技能十一 准确描述v-if和v-show的区别
v-if 和 v-show 是 Vue 中用于条件渲染的指令,它们有一些区别:
- 渲染方式:
v-if:条件判断为真时,才会渲染对应的元素到 DOM 中。如果条件为假,那么元素不会被渲染,并且从 DOM 中移除。v-show:条件判断为真时,会直接将元素的 CSS 的display属性设置为显示(通常为display: block),隐藏时设置为隐藏(通常为display: none),元素仍然会存在于 DOM 中,只是在页面中不可见。
- 切换频率:
v-if:在条件发生变化时,条件判断会重新执行,如果条件为真,则渲染元素到 DOM 中;如果条件为假,则从 DOM 中移除元素。因此,v-if的切换开销较高。v-show:在条件发生变化时,只需要简单地切换元素的display属性,因此切换开销较低。
- 初始渲染开销:
v-if:在初始渲染时,如果条件为假,元素不会被渲染到 DOM 中,因此初始渲染开销较低。v-show:在初始渲染时,元素会根据条件的真假进行渲染并设置相应的display属性,因此初始渲染开销较高。
- 条件复杂度:
v-if:适用于条件复杂、需要频繁切换的情况,因为每次条件判断都会重新渲染 DOM。v-show:适用于条件简单、需要频繁切换的情况,因为只是简单地切换 CSS 的display属性,不涉及 DOM 的重新渲染。
基于以上区别,可以根据具体的需求选择使用 v-if 还是 v-show。如果在页面中频繁切换元素的显示和隐藏,且元素结构比较简单,你可以使用 v-show 来实现,以提高性能和渲染速度;如果条件复杂且切换频率较低时,可以使用 v-if,以减少 DOM 中元素的冗余和计算开销。
技能十二 合理拆分store并配置全局getter
Vue 应用中,为了更好地管理状态,我们可以将 Vuex 的 store 拆分为多个模块,并配置全局的 getter。这样可以使状态管理更加模块化和可维护。下面是一种合理拆分 store 并配置全局 getter 的做法:
- 创建模块文件:
首先,创建多个模块文件来拆分 store。每个模块文件都包含自己的状态、mutations、actions 等。例如:
// user.js
const state = {
currentUser: null,
isLoggedIn: false,
};
const mutations = {
setCurrentUser(state, user) {
state.currentUser = user;
state.isLoggedIn = !!user;
},
};
const actions = {
login({ commit }, userCredentials) {
// 处理登录逻辑
commit('setCurrentUser', loggedInUser);
},
};
export default {
namespaced: true,
state,
mutations,
actions,
};
**
- 配置全局 getter:
在根模块的 store 文件中,通过 Vuex 的createNamespacedHelpers方法创建命名空间辅助函数,然后使用它来定义全局 getter。例如:
import { createStore, createNamespacedHelpers } from 'vuex';
import userModule from './modules/user';
const { mapGetters } = createNamespacedHelpers('user');
const store = createStore({
modules: {
user: userModule,
// 其他模块
},
getters: {
...mapGetters(['isLoggedIn']),
// 其他全局 getter
},
});
export default store;
**
在上述代码中,我们首先利用 createNamespacedHelpers 方法创建了一个 mapGetters 函数,用于将模块中的 getter 映射到全局。然后,在 getters 配置中使用这个函数来导入模块的 getter,并添加到全局的 getter 中。
- 在组件中使用全局 getter:
最后,在组件中可以通过mapGetters辅助函数来获取全局的 getter,并方便地在模板或组件中使用。例如:
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters(['isLoggedIn']),
},
};
**
现在,isLoggedIn 全局 getter 就可以在这个组件中使用了。
通过以上步骤,我们成功地进行了 Vuex store 的拆分,并配置了全局的 getter。这样的做法可以让状态管理更加模块化和可维护,使代码结构更清晰。
技能十三 清晰描述在组件中调用action发请求的流程-画图
组件中调用 action 发送请求的流程的简单示意图:
+--------------------------------------------------+
| |
| Vue Component |
| |
| +----------+ |
| | Actions | |
| +----+-----+ |
| | |
| v |
| +----+-----+ |
| | Mutations| |
| +----+-----+ |
| | |
| v |
| +----+-----+ |
| | State | |
| +----------+ |
| |
+------------------+-------------------------------+
|
v
+----------+----------+
| API Service |
+----------+----------+
|
v
+----------+----------+
| Server |
+----------------------+
**
- Vue Component: Vue 组件,如一个页面组件,包含视图和用户交互逻辑。
- Actions: 从组件中调用的 Vuex actions。在这里,我们可以调用 API service 中定义的方法。
- Mutations: Vuex 的 mutations,用于修改 state 中的数据。
- State: Vuex 的 state,存储应用程序的数据。
- API Service: 封装了对后端 API 的访问方法,以发送请求和接收响应。
- Server: 后端服务器,用于处理请求并提供所需的数据。
流程:
- Vue 组件中触发事件或调用方法,通常是通过按钮点击或其他用户交互行为。
- 在组件中调用 Vuex 的 actions。这些 actions 可以接收参数并执行相应的逻辑。
- 在 actions 中,可以调用 API service 中的方法,使用适当的参数发起请求到后端服务器。
- API service 封装了与后端 API 的交互,发送请求并处理响应。可以使用 Axios 或其他 HTTP 库来进行网络请求。
- 后端服务器根据请求处理并返回相应的数据。
- API service 接收到响应后,可以对数据进行处理,并将结果传递给调用它的 actions。
- Actions 根据接收到的数据,调用适当的 mutations 对 Vuex 的 state 进行修改,更新应用程序的数据。
- Vue 组件会响应 state 的变化,并根据更新后的数据重新渲染视图。
在整个流程中,组件通过调用 actions,间接触发了 API service 中的请求,并在得到响应后,通过 mutations 更新了应用程序的 state,最终导致了组件视图的更新。
请注意,上述流程中的具体实现细节可能会根据项目的不同而有所不同,但基本的概念和流程是相似的。
技能十四 掌握两种页面跳转传值的方式-query¶ms-画图
两种页面跳转传值方式(query 和 params)的简单示意图:
- Query 参数传值:
+------------------------+ +------------------------+
| | | |
| Source Page | | Target Page |
| | | |
| +----------------------->| |
| | | |
| | | | |
| | query 参数传值 | | |
| | | | |
| | | |
| | | | |
| | 跳转到目标页面 | | |
| | | | |
| | | |
+------------------------+ +------------------------+
**
- Source Page: 源页面,发起页面跳转的页面。
- Target Page: 目标页面,接收跳转并传递的参数的页面。
流程:
- Source Page 构建目标页面的跳转链接,将需要传递的参数添加为查询参数。
- 点击跳转链接或通过编程方式进行页面跳转。
- Target Page 接收到跳转的请求,并从 URL 中解析出查询参数。
- Target Page 根据查询参数进行相应的处理,使用传递的参数完成操作。
- Params 参数传值:
+------------------------+ +------------------------+
| | | |
| Source Page | | Target Page |
| | | |
| | | | |
| | params 参数传值 | | |
| | | | |
| +----------------------->| |
| | | |
| | | | |
| | 跳转到目标页面 | | |
| | | | |
| | | |
| | | | |
| | 接收并处理 params | | |
| | | | |
+------------------------+ +------------------------+
**
- Source Page: 源页面,发起页面跳转的页面。
- Target Page: 目标页面,接收跳转并传递的参数的页面。
流程:
- Source Page 构建目标页面的跳转链接,并将需要传递的参数添加为路径参数。
- 点击跳转链接或通过编程方式进行页面跳转。
- Target Page 接收到跳转的请求,并从 URL 中解析出路径参数。
- Target Page 根据路径参数进行相应的处理,使用传递的参数完成操作。
需要注意的是,Query 参数传值是通过在 URL 上添加查询参数来传递数据,而 Params 参数传值是通过将参数作为路径的一部分来传递数据。具体选择哪种方式取决于项目需求和个人偏好。
技能十五 掌握组件之间传值的方式>8种(写代码)
以下是8种常见的组件之间传值的方式,附带简单的代码示例:
- Props(父组件向子组件传值):
// ParentComponent.vue
<template>
<ChildComponent :message="message" />
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
message: 'Hello from parent'
};
}
};
</script>
// ChildComponent.vue
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
props: {
message: String
}
};
</script>
**
- $emit(子组件向父组件传值):
// ChildComponent.vue
<template>
<button @click="sendMessage">Send Message</button>
</template>
<script>
export default {
methods: {
sendMessage() {
this.$emit('message', 'Hello from child');
}
}
};
</script>
// ParentComponent.vue
<template>
<ChildComponent @message="receiveMessage" />
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
methods: {
receiveMessage(message) {
console.log(message);
}
}
};
</script>
**
- $refs(父组件访问子组件的方法和数据):
// ParentComponent.vue
<template>
<div>
<ChildComponent ref="child" />
<button @click="callChildMethod">Call Child Method</button>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
methods: {
callChildMethod() {
this.$refs.child.childMethod();
}
}
};
</script>
// ChildComponent.vue
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello from child'
};
},
methods: {
childMethod() {
console.log('Child method called');
}
}
};
</script>
**
- Provide/Inject(祖先组件向后代组件传值):
// AncestorComponent.vue
<template>
<div>
<descendant-component />
</div>
</template>
<script>
import DescendantComponent from './DescendantComponent.vue';
export default {
provide() {
return {
message: 'Hello from ancestor'
};
},
components: {
DescendantComponent
}
};
</script>
// DescendantComponent.vue
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
inject: ['message']
};
</script>
**
- Vuex(通过集中式状态管理传递数据):
// ComponentA.vue
<template>
<div>{{ message }}</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
computed: {
...mapState(['message'])
}
};
</script>
// ComponentB.vue
<template>
<button @click="updateMessage">Update Message</button>
</template>
<script>
import { mapMutations } from 'vuex';
export default {
methods: {
...mapMutations(['updateMessage']),
updateMessage() {
this.updateMessage('Hello from ComponentB');
}
}
};
</script>
// store/index.js
import { createStore } from 'vuex';
export default createStore({
state: {
message: 'Hello from ComponentA'
},
mutations: {
updateMessage(state, payload) {
state.message = payload;
}
}
});
**
- Event Bus(通过事件总线传递数据):
// EventBus.js
import { reactive } from 'vue';
const eventBus = reactive({});
export default eventBus;
// ComponentA.vue
<template>
<div>{{ message }}</div>
</template>
<script>
import eventBus from './EventBus.js';
export default {
data() {
return {
message: ''
};
},
created() {
eventBus.$on('update-message', (message) => {
this.message = message;
});
}
};
</script>
// ComponentB.vue
<template>
<button @click="updateMessage">Update Message</button>
</template>
<script>
import eventBus from './EventBus.js';
export default {
methods: {
updateMessage() {
eventBus.$emit('update-message', 'Hello from ComponentB');
}
}
};
</script
技能十六 能说出$nextTick的具体应用场景>2个
nextTick 的两个常见应用场景:
- 操作 DOM 元素:
当 Vue 更新 DOM 后,通过 nextTick 的回调函数中,以确保操作的准确性。
mounted() {
this.$nextTick(() => {
const element = document.getElementById('myElement');
console.log(element.offsetWidth); // 获取元素的宽度
});
}
**
- 更新组件数据后的回调:
有时候,我们需要在更新组件的数据后进行一些操作。但是由于 Vue 是异步更新 DOM 的,直接在数据更新后执行操作可能会导致操作的时机不准确。此时,可以使用 $nextTick 来确保在更新数据之后执行操作。
methods: {
updateData() {
this.message = 'New message'; // 更新数据
this.$nextTick(() => {
console.log('Data updated and DOM refreshed');
// 在数据更新后执行后续操作
});
}
}
**
通过在 $nextTick 的回调函数中执行操作,我们可以确保在 Vue 更新 DOM 后执行代码,从而保证操作的正确性和实时性。
总而言之,$nextTick 的主要应用场景是确保在 DOM 更新完成后执行代码,特别是在操作 DOM 元素或基于已更新数据的操作时。
技能十七 能举例说出$set的应用场景>1个
set 的一个应用场景:
动态添加响应式属性:
当我们需要在 Vue 实例的数据对象中动态添加一个新的属性,并希望该属性是响应式的,这时就可以使用 $set 方法。
例如,假设我们有一个列表,在用户点击按钮时,需要动态地向列表中添加新项,并希望新项是响应式的。在这种情况下,我们可以使用 $set 方法来添加响应式属性。
<template>
<div>
<ul>
<li v-for="item in list" :key="item.id">{{ item.name }}</li>
</ul>
<button @click="addItem">Add Item</button>
</div>
</template>
<script>
export default {
data() {
return {
list: [] // 假设初始列表为空
};
},
methods: {
addItem() {
const newItem = { id: 1, name: 'New Item' };
this.$set(this.list, this.list.length, newItem);
}
}
};
</script>
**
在上述示例中,点击 “Add Item” 按钮时,我们通过 set,我们确保添加的新项是响应式的,并且可以正常地触发相关的依赖追踪和更新视图。
总结:$set 的主要应用场景是通过将新的属性添加到响应式对象中,使新的属性也是响应式的,并触发数据的更新和视图的重新渲染。
技能十八 能使用vue.use对自定义组件|指令进行快速注册
当使用 Vue.use() 方法时,我们可以快速注册自定义组件和指令,并使它们在应用程序中全局可用。下面是使用 Vue.use() 注册自定义组件和指令的示例。
// 自定义组件
import MyComponent from './components/MyComponent.vue';
// 自定义指令
import MyDirective from './directives/MyDirective';
// 注册自定义组件和指令
Vue.use(MyComponent);
Vue.use(MyDirective);
**
在示例中,我们假设 MyComponent 是一个自定义组件,文件路径为 './components/MyComponent.vue',MyDirective 是一个自定义指令,文件路径为 './directives/MyDirective'。通过 import 导入这些组件和指令。
然后,使用 Vue.use() 方法对这些组件和指令进行注册。这将使它们在整个应用程序中可用,无需在每个组件中单独导入和注册。
注意:在自定义组件中使用 Vue.use() 的前提是要确保组件正确地声明了 install 方法。例如,对于自定义组件 MyComponent,需要在组件的脚本部分中添加以下代码:
MyComponent.install = function (Vue) {
Vue.component(MyComponent.name, MyComponent);
};
**
通过这样的方式,当我们使用 Vue.use(MyComponent) 注册组件时,会自动调用 MyComponent 的 install 方法,将组件注册到全局。对于自定义指令,可以直接通过 Vue.directive() 进行注册,无需声明 install 方法。
使用 Vue.use() 可以让我们更方便地注册自定义组件和指令,并在整个应用程序中全局使用它们。
技能十九 能描述v2响应式的缺陷及对应proxy的优点(有代码)
Vue 2.x 的响应式系统通过使用 Object.defineProperty 来追踪属性的读取和修改,从而实现数据响应式。然而,Vue 2.x 的响应式系统存在一些缺陷,而使用 ES6 的 Proxy 对象来代替 Object.defineProperty 可以解决这些问题并带来一些优点。
缺陷:
- 新增属性和删除属性的监听:Vue 2.x 的响应式系统只能追踪已经存在的属性,无法监听新增的属性或删除的属性,需要使用
$set方法来解决。这给开发过程带来了一些不便。 - 数组的变更监听:Vue 2.x 的响应式系统对数组的变更监听存在一些限制。例如,直接通过下标修改数组元素或修改数组长度等不会触发视图更新,需要使用特定的数组变异方法(如
push、pop、splice等)来更新数组。
Proxy 的优点:
- 支持新增属性和删除属性的监听:Proxy 对象可以监听到新增属性和删除属性的操作,不需要特殊的方法来处理。这使得对于动态属性的追踪更加自然和方便。
- 支持数组的变更监听:Proxy 对象对数组的操作都可以被监听到,包括直接通过下标修改元素和修改数组长度等操作。这消除了 Vue 2.x 数组变更监听的限制,使得对于数组的操作更加灵活和方便。
下面是使用 Proxy 对象的示例代码,展示了 Proxy 的优点:
// 数据对象
const data = {
message: 'Hello',
count: 0,
list: []
};
// 创建 proxy 对象
const proxyData = new Proxy(data, {
get(target, key) {
console.log('Get operation:', key);
return target[key];
},
set(target, key, value) {
console.log('Set operation:', key, value);
target[key] = value;
// 触发视图更新等操作
},
deleteProperty(target, key) {
console.log('Delete operation:', key);
delete target[key];
// 触发视图更新等操作
}
});
// 对 proxyData 的读取和修改将触发 Proxy 的 get 和 set 操作
console.log(proxyData.message); // 触发 Get 操作,打印 'Hello'
proxyData.count = 1; // 触发 Set 操作,打印 'Set operation: count 1'
// 对数组的操作将触发 Proxy 的 set 和其他对应操作
proxyData.list.push('Item 1'); // 触发 Set 操作和其他对应操作
**
在上述示例中,我们使用 Proxy 对象来创建 proxyData,对 proxyData 的读取和修改操作将触发相应的 Proxy 操作。Proxy 对象自带了对新增属性和删除属性的监听以及对数组的变更监听的能力。
总结:Vue 2.x 的响应式系统存在一些限制,而使用 Proxy 对象可以解决这些限制,并带来一些优点,如对新增属性和删除属性的监听以及对数组的变更监听等。Proxy 对象使得数据追踪更加方便和灵活,能够提升开发效率和代码可读性。
技能二十 能表述v3和v2的区别?
Vue 3 和 Vue 2 是 Vue.js 框架的两个主要版本,它们在一些关键方面有着重大的区别。下面是 Vue 3 相对于 Vue 2 的几个重要区别:
-
响应式系统的升级:
- Vue 3 使用了基于 Proxy 的响应式系统,代替了 Vue 2 中使用 Object.defineProperty 的方式。Proxy 可以跟踪属性的新增、删除以及深层属性的变化,相比较 Vue 2 的限制更灵活且性能更高。
- Vue 3 的响应式系统还引入了新的
ref和reactive语法,使得对数据的定义更加直观和简单。
-
更好的性能:
- Vue 3 对编译器进行了优化,使用了静态标记和编译时优化等方法,提升了运行时渲染效率。
- 在虚拟 DOM 的比对算法上,Vue 3 在一些常见场景下也有更好的性能表现。
-
更小的体积:
- Vue 3 经过重构,去除了一些不常用的 API,减少了代码的体积。
- Vue 3 采用了更加精细的模块化,可以实现更好的代码组织和按需加载,减少了对整个框架的依赖。
-
Composition API:
- Vue 3 引入了 Composition API,提供了一种基于函数的组件组织方式,使得代码更可读、可维护和可复用。这种方式可以解决 Vue 2 中复杂组件逻辑难以维护的问题。
-
TypeScript 支持:
- Vue 3 对 TypeScript 的支持更加友好,从内部结构到 API 声明都有相应的优化和改进。Vue 3 提供了完整的 TypeScript 类型定义,使得在使用 TypeScript 开发时更加便捷和安全。
总的来说,Vue 3 在性能、体积、响应式系统和代码组织等方面有着明显的改进和优化,提供了更好的开发体验和性能表现。然而,由于一些 API 的变化和新特性的引入,迁移到 Vue 3 可能需要一定的学习和改造成本。