文末
篇幅有限没有列举更多的前端面试题,小编把整理的前端大厂面试题PDF分享出来,一共有269页
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
到这里可能有同学会问,装饰器能加参数吗,比如,我们需要通过参数来判断装饰器需不需要生效,小伙子,灵活点嘛,既然只要后面跟着的是一个函数,那么我们把函数作为返回值,不就可以接收参数了,只要能接收参数,那么里面自然就可以加逻辑,加判断,具体例子如下:
function UserConfig(params: string) {
return function(constructor: any) {
constructor.prototype.name = () => {
console.log(params);
};
};
}
@UserConfig(“oliver”)
class User {
constructor() {
console.log(“你好”);
}
}
const user = new User();
(user as any).name();
到这里,相信各位小伙伴对装饰器有一定了解了吧,现在我们来看一个复杂一点的例子
// 定义一个接口,约束类
interface UserInterface {
name: string;
}
// 定义一个装饰器
function UserConfig<T extends new (…arg: any[]) => any>(constructor: T) {
return class extends constructor {
name = “lilei”;
};
}
// 定义类并使用装饰器
@UserConfig
class User implements UserInterface {
name: string;
constructor(name: string) {
this.name = name;
}
}
const user = new User(“oliver”);
console.log(user.name);
解释一下这段代码:
1)最上面1-4行,定义了一个接口;
2)6-11行,定义了一个装饰器,它的类型比较奇怪,是一个泛型,泛型T,继承了一个new (…arg: any[]) => any这个东西,然后,就是它有一个参数,参数的类型也是泛型T,这个装饰器函数返回了一个类,并且这个类继承了我们的传入的构造函数,这也就是一旦使用我们的装饰器去装饰了某个类,那么这个类上面所有的属性,方法都会被继承,到这里应该没什么问题吧,并在在这个装饰器内部,将其name属性的值改写为了lilei;
那么这里的难点就剩下:new (…arg: any[]) => any,这个是啥,通过关键字new,我们知道这是一个构造函数,并且后面这个也应该是一个函数,…arg: any[]这其实是一个参数的简写,代表着这个构造函数接收任意多的参数,之后这些参数被转成了数组的形式,并且因为我们不知道这个数组的每一项究竟是什么,因此这个数组的类型就是由any组成的数组;
综合的说,new (…arg: any[]) => any,这段代码就是它的类型是一个构造函数,这个构造函数的参数是任意多且任意类型,并且返回值也是任意类型;
3)14行,使用了装饰器
4)15-20行,定义了一个类,它接受接口UserInterface的约束,并且它有一个属性,属性的key是name,值是实例化的时候传入的;
5)21行,实例化了一个user,传入的值是oliver;
6)22行,打印name值
通过打印,结果值是lilei,可以理解吧,因为装饰器将其的值改写了;
正如上面所说,装饰器是一个函数,既然类装饰器装饰的是类,方法装饰器自然装饰的是方法,看个例子先:
interface UserInterface {
name: string;
getName: () => string;
}
function nameConfig(state: boolean = false) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target, propertyKey);
};
}
class User implements UserInterface {
name: string;
constructor(name: string) {
this.name = name;
}
@nameConfig()
getName() {
return this.name;
}
}
如示例,方法装饰器就是将装饰器放在方法上,同样,方法装饰器执行的时机是在方法被定义的时候,而不是被实例化的时候,另外方法装饰器的执行是在类装饰器之前的,如上面这段代码所示,运行的时候会直接执行console.log;
通过例子知道,方法装饰器一共有三个参数,分别是target,propertyKey,descriptor,我们分别解释一下:
-
propertyKey,这个就是方法名,例子中就是getName这个方法名;
-
target,这个其实要看具体情况,看这个方法是不是静态方法,也就是这个方法带不带static,看个例子
function nameConfig(state: boolean = false) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target);
};
}
class User implements UserInterface {
name: string;
constructor(name: string) {
this.name = name;
}
// target对应的是类的prototype
@nameConfig()
getName() {
return this.name;
}
// target对应的是类的构造函数
@nameConfig()
static getName() {
return this.name;
}
}
- descriptor,这个东西就比较复杂了,它和Object.defineProperty()的第三个参数是一样的,具体可以看MDN上的这个解释:Object.defineProperty(),简单的说,就是用来定义或修改属性描述符的,看一个简单的例子吧:
interface UserInterface {
name: string;
getName: () => string;
}
function nameConfig(state: boolean = false) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 将这个writable这个属性描述符设置成false,代表不可以重写个方法
descriptor.writable = false;
};
}
class User implements UserInterface {
name: string;
constructor(name: string) {
this.name = name;
}
@nameConfig()
getName() {
return this.name;
}
@nameConfig()
static getName() {
return this.name;
}
}
const user = new User(“oliver”);
// 重写getName方法
user.getName = () => {
return “new Oliver”;
};
console.log(user.getName());
我们知道,方法是可以重写的,如果我们不开放重写方法的功能,那么我们就可以将writeable这个属性描述符设置成false,代表不可以重写;
直接看例子吧,用法和执行时机和类装饰器、方法装饰器一致:
interface UserInterface {
name: string;
}
function nameConfig(state: boolean = false) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 不可修改
descriptor.writable = false;
};
}
class User implements UserInterface {
constructor(private _name: string) {}
get name() {
return this._name;
}
@nameConfig()
set name(name: string) {
this._name = name;
}
}
const user = new User(“oliver”);
// 修改name属性
user.name = “new oliver”;
console.log(user.name);
你没看错,用法和方法装饰器几乎一摸一样,在例子中,我们同样设置了writeable为不可编辑,那么修改直接会报错;
也同样直接看例子吧,用法和执行时机和类装饰器、方法装饰器一致,另外值得注意的是,属性装饰器的是最先执行的,它执行的比方法装饰器和类装饰器这些要早,区别在于,属性装饰器是没有descriptor这个属性的,它只有target和propertyKey
function nameConfig(state: boolean = false): any {
return function(target: any, propertyKey: string) {
console.log(target, propertyKey);
};
}
class User implements UserInterface {
@nameConfig()
name = “Oliver”;
}
const user = new User();
user.name = “new oliver”;
console.log(user.name);
肯定有小伙伴问,能在装饰器中直接修改值吗,比如:
interface UserInterface {
name: string;
}
function nameConfig(state: boolean = false): any {
// target是原型
return function(target: any, propertyKey: string) {
target[propertyKey] = “new Oliver”;
};
}
class User implements UserInterface {
@nameConfig()
name = “Oliver”;
}
const user = new User();
console.log(user.name);
实际上是不行的,我们的target指的是原型,不信的话可以看,多打印一个
console.log(user.name); // Oliver
console.log((user as any).proto.name); // new Oliver
所以,这里要注意一下,并不能直接改值,另外,还会有小伙伴问,如果这个装饰器没有第三个参数,有没有办法和方法装饰器一样,禁止修改,比如:
class User implements UserInterface {
@nameConfig()
name = “Oliver”;
}
const user = new User();
user.name = “new oliver”; // 需要这里直接报错
答案肯定是有的
interface UserInterface {
name: string;
}
function nameConfig(state: boolean = false): any {
// target是原型
return function(target: any, propertyKey: string) {
// 改写name的属性描述符
const descriptor: PropertyDescriptor = {
writable: false
};
return descriptor;
};
}
class User implements UserInterface {
@nameConfig()
name = “Oliver”;
}
const user = new User();
user.name = “new oliver”; // 这里运行的时候会直接报错
console.log(user.name);
我们自己定义了一个描述符,然后将其返回,它的意思就是替换掉原来的属性描述符,这样外界在修改的时候会直接报错;
再同样,用法和执行时机和类装饰器、方法装饰器一致,在装饰器执行顺序中,参数装饰器是在方法装饰器执行完毕后执行,区别在于,参数装饰器是有第三个参数,但是不是descriptor是parameterIndex,target和propertyKey一摸一样,parameterIndex代表的是当前参数是第几个参数,类型是number
interface UserName {
name: string;
}
interface UserNameUserInterface {
name: string;
getName: (key: T) => string;
}
function nameConfig(state: boolean = false): any {
// target是原型
return function(target: any, propertyKey: string, parameterIndex: number) {
console.log(target, propertyKey, parameterIndex);
};
}
class User implements UserNameUserInterface {
constructor(public name: string) {}
getName(@nameConfig() key: T): string {
return this[key];
}
}
const user = new User(“Oliver”);
console.log(user.getName(“name”));
这里有用到一个关键字keyof,如果有小伙伴不大明白用法,可以看博主的另外一篇博文:【问题解答】在TypeScript中keyof怎么理解,通过例子我们可以看到,参数装饰器就是直接加在参数前面;
其他的用法其实都差不多,有兴趣的小伙伴可以参照上面别的装饰器的例子自己写写;
=============================================================
本小结主要的目的是为了做一个实际的小练习,可以更好的掌握上面的装饰器知识~
我们实际项目开发中,比如后台接口的返回,理论上接口A的user属性应该返回的是一个对象,代表的是当前这个用户的用户信息,但是,如果接口异常了导致某些特殊情况下返回了一个null,那么前台如果不做非空判断,可能代码就直接会报错,比如:
// 假设接口返回的值
const getUserPromise: any = null;
ES6
-
列举常用的ES6特性:
-
箭头函数需要注意哪些地方?
-
let、const、var
-
拓展:var方式定义的变量有什么样的bug?
-
Set数据结构
-
拓展:数组去重的方法
-
箭头函数this的指向。
-
手写ES6 class继承。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
微信小程序
-
简单描述一下微信小程序的相关文件类型?
-
你是怎么封装微信小程序的数据请求?
-
有哪些参数传值的方法?
-
你使用过哪些方法,来提高微信小程序的应用速度?
-
小程序和原生App哪个好?
-
简述微信小程序原理?
-
分析微信小程序的优劣势
-
怎么解决小程序的异步请求问题?
其他知识点面试
-
webpack的原理
-
webpack的loader和plugin的区别?
-
怎么使用webpack对项目进行优化?
-
防抖、节流
-
浏览器的缓存机制
-
描述一下二叉树, 并说明二叉树的几种遍历方式?
-
项目类问题
-
笔试编程题:
最后
技术栈比较搭,基本用过的东西都是一模一样的。快手终面喜欢问智力题,校招也是终面问智力题,大家要准备一下一些经典智力题。如果排列组合、概率论这些基础忘了,建议回去补一下。