前端题目思考总结

149 阅读11分钟

实现 lodash _.get 方法

JS 和 TS 的区别

  • TS 多出来的类型:tuple、enum、any
  • TS 是 JS 的超集,可以被编译为 JS 代码,完全兼容 JavaScript
  • JS 是基于对象的,一种脚本语言。TS 基于面向对象编程,引入了很多面相对象程序设计的特征 (所以对于熟悉C#、Java和所有强类型语言的开发者来说,TS 容易上手),包括
    • interface 接口
    • classes 类
    • enumerated types 枚举类型
    • generics 泛型
    • modules 模块
  • TS 支持可选参数,JS 则不支持该特性
  • TS 支持静态类型,JS 不支持
  • TS 支持接口,JS 不支持
  • TS 在开发时编译可以知道错误,JS 错误则需要在运行时才能暴露
  • TS 作为强类型语言,可以明确知道数据类型,代码可读性强。
JSTS
JS 不支持强制类型或静态类型TS 支持强制类型或静态类型
只是一种脚本语言支持面相对象的编程概念,如类、接口、继承、泛型
没有可选参数特性有可选参数特性
是解释语言,在运行的时候才会暴露错误编译代码并在开发期间暴露错误
不支持泛型支持泛型
不支持 ES6支持 ES6
不支持模块支持模块??
number 和 string 是对象number 和 string 是接口??

泛型

泛型 : 在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。

我们给identity添加了类型变量TT帮助我们捕获用户传入的类型(比如:number), 之后我们就可以使用这个类型。 之后我们再次使用了 T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。

function identify<T>(arg: T): T {
    return arg
}
  • 泛型类
class GenericNumber<T> {
	zeroValue: T;
	add: (x: T, y: T) => T;
} 

let genericNumber = new GenericNumber<number>();
genericNumber.zeroValue = 0
genericNumber.add = function (x, y) {
    return x + y
}
console.log(genericNumber.add(1, 3))
  • 泛型函数
  • 泛型方法
  • 泛型接口

TypeScript 基本数据类型

  • enum 枚举
  • tuple 元组
  • never
  • void
  • boolean
  • number
  • string
  • null undefined
    • typeof null === object
  • array
  • any

TS 接口 有什么特性

TypeScript 原则之一是对值所具有的结构进行类型检查。 接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约

interface interface_name {
    // 字段声明
    // 方法声明
}

接口只是声明方法和字段

特性

  • 可选属性 ?
  • 只读属性
interface Point {
    readonly x: number
}
  • 类类型
  • 继承接口:一个接口可以继承多个接口

ts 断言

  • 尖括号类型断言: <类型>变量名
  • as 操作符 变量名 as 类型
    • 值 as 类型

JSX

JSX是一种嵌入式的类似XML的语法。 它可以被转换成合法的JavaScript, 尽管转换的语义是依据不同的实现而定的。 JSX因React框架而流行,但也存在其它的实现。 TypeScript支持内嵌,类型检查以及将JSX直接编译为JavaScript。

基本用法

  1. 给文件一个 .tsx 扩展名
  2. 启用 .jsx 选项

as 操作符

因为TypeScript也使用尖括号来表示类型断言,在结合JSX的语法后将带来解析上的困难。 因此,TypeScript在.tsx文件里禁用了使用尖括号的类型断言。在 .tsx文件里使用另一个类型断言操作符:as

as 操作符与尖括号类型断言行为是等价的

declare: 声明

  • 在 ts 中使用第三方库,需要声明,否则报错
    • jQuery wx
  • 声明文件:.d.ts

ts.xcatliu.com/basics/decl…

装饰器

segmentfault.com/a/119000002… www.tslang.cn/docs/handbo…

ES5 和 ES6 继承有什么区别

  • 写法不同,ES5 继承麻烦不直观,ES6 比较直观
    • ES5 继承是通过原型链来实现的
    • ES6 为了直观模拟 class 来继承的,本质上是也是 prototype 继承的语法糖。
  • ES5 不可以继承原生构造函数(RegExp Boolean Array Number String Object Date Function Error ),ES6 可以。原因是两者实现方式不同
    • 子类无法获取原生构造函数的内部属性
    • ES5 先实例化建立子类对象的this,再将父类属性添加到子类
    • ES6 是先新建父类的实例对象this,然后再用子类的构造函数修饰this

参考:

ES6 class 继承

ES5 ES6 继承的区别

如何将两个 TS 合并到一个 JS 中

tsc --outFile common.js ts1.ts ts2.ts

image.png

TypeScirpt 常见题目

cookies,localStorage,sessionStorage,indexedDB区别

特性cookieslocalStoragesessionStorageindexedDB
大小4kPC 5M,移动端 2.5MPC 5M,移动端 2.5M一般没有大小限制
与服务器通信每次HTTP请求,都会携带在Header中,对于请求性能有影响不参与不参与不参与
易用性原生操作比较麻烦,字符串拆解较为易用,键值对总是以字符串的形式存储: setItem getItem较为易用,键值对总是以字符串的形式存储: setItem getItem比较繁琐,支持事务,操作比较麻烦
生命周期服务器设置时效性除非手动或js删除,否则持久化存储,一直有效关闭浏览器或者页面失效永久
作用域所有同源窗口共享所有同源窗口共享不能在不同的浏览器窗口共享所有同源窗口共享
同源策略同源同源同源同源

Service Worker

  1. Service Worker 是运行在浏览器背后的独立线程,不会造成阻塞,一般可以用来实现缓存功能
  2. 使用 Service Worker 的话,传输协议必须为 HTTPS
  3. 设计为完全异步,同步 API (XHR 或 localStorage)不能在 service worker 中使用
  4. 实现步骤:
    • 注册 ServiceWorkerContainer.register()
    • 安装:当 oninstall 事件的处理程序执行完毕后,可以认为 service worker 安装完成了。
    • 激活:当 service worker 安装完成后,会接收到一个激活事件(activate event)。 onactivate 主要用途是清理先前版本的 service worker 脚本中使用的资源。

注意:页面初次加载注册成功后,当前页面的 Service Worker 是不能生效的, 因为 register() 成功后的打开的页面才可以让 Service Worker 控制页面, 页面需要重新加载才可以让 Service Worker 生效

Cookie Session Token 如何选择

Cookie Session Token 存在的原因: HTTP 无状态,不同请求无法知道是否有关

  • Cookie: 保存到客户端,有状态,4k 大小限制

    • Cookie 有三个目的
      • 会话管理: 登录、购物车、游戏得分或者浏览器应该记录的内容
      • 个性化:用户偏好主题或者其他设置
      • 追踪:记录和分析用户行为

        广告跟踪。你上网的时候肯定看过很多的广告图片, 这些图片背后都是广告商网站(例如 Google),它会“偷偷地”给你贴上 Cookie 小纸条, 这样你上其他的网站,别的广告就能用 Cookie 读出你的身份,然后做行为分析,再推给你广告。

        • 就好比,淘宝在新浪微博和百度搜索中都租了广告位,然后在新浪微博记录个人的信息,然后, 当你去到百度搜索页面的时候,会利用在新浪微博收集的个人信息,显示精准广告。
    • 属性:HttpOnly(禁止 document.cookie 使用)、SameSite可以防范跨站请求伪造(XSRF)
    • 风险:浏览器和用户可能会禁用Cookie
    • 替代方案: URL 参数
  • Session: 通过 sessionID 保存到服务器端内存中,对分布式、跨系统不友好

  • Token: 服务端通过算法和一些信息生成的字符串,给客户端进行请求的令牌

    • 优点
      • 无状态:tokens 里存储用户验证信息,不需要存储 session 信息
      • 可扩展:提供可选权限给第三方应用程序,如 github token
      • 安全性(时效性): 有时效性,或者通过token revocation 使之失效
      • 多平台跨域:跨域

选择:

  • cookie/session:系统足够小,用户少,简单、易维护
  • token: 系统同时登录用户多,集群服务器多,PC移动平板多端同步,有单点登录需求

扩展:JWT (Json Web Token)

参考:

blog.mimvp.com/article/394…

www.cnblogs.com/cxuanBlog/p…

time.geekbang.org/column/arti…

0.1 + 0.2 等于 0.3吗?为什么?如果不等于如何解决?

不等于 0.3。 0.1 和 0.2 这两个实数无法用二进制精确表示。在计算的过程中,会发生精度丢失。

为什么 0.1 + 0.2 != 0.3 呢?首先,0.1 和 0.2 这两个实数无法用二进制精确表示。 在二进制的世界里,只有包含因子 5 的十进制数才有可能精确表示, 而 0.1 和 0.2 转换为二进制后是无限循环小数,在计算机中存储的值只能是真实值的近似表示, 这里是第一次精度丢失;其次,计算机浮点数采用了 IEEE 754 标准格式,IEEE 754 严格规定了尾数域指数域可表示的大小, 位数有限,意味着可表示的信息量是有限的,换句话说就会存在三种误差:上溢、下溢和舍入误差。 而 0.1 + 0.2 的结果的尾数域部分刚好超过了尾数域位数,超过位数的部分舍去,存在舍入误差, 这里是第二次精度丢失。

  1. 0.1 和 0.2 转成双精度二进制浮点数时,由于二进制浮点数的小数位只能存储52位, 导致小数点后第53位的数要进行为1则进1为0则舍去的操作,从而造成一次精度丢失。
  2. 0.1和0.2转成二进制浮点数后,二进制浮点数相加,小数位相加导致小数位多出了一位, 又要让第53位的数进行为1则进1为0则舍去的操作,又造成一次精度丢失

引起BUG:

统计页面显示错误,比如300.01元的产品,优惠了300元,剩下的不是0.01,无法付费 image.png

解决办法:

  1. 使用现有的运算库:bignumber.js Math.js
  2. 先把数添加10倍,做运算后,再除以10

拓展:

0.1 + 0.1 = 0.2 吗?为什么?

参考资料

react router 原理

参考资料

ES6 中的 proxy 和 reflect 作用

通过拦截修改默认操作,相当于改写编程语言

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

Proxy 可以理解成,在目标对象之前架设一层拦截,外界对该对象的访问, 都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。 Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

实例:使用 Proxy 实现观察者模式

const queueObservers = new Set();
// 观察到变化自动执行的函数列表
const observe = (fn) => queueObservers.add(fn); // 1. 将需要触发的函数添加到列表中(Set)
// 观察目标
const observable = (obj) => new Proxy(obj, { set });// 2. 通过 Proxy 对观察目标和观察行为封装绑定

function set(target, key, value, receiver) {
	let result = Reflect.set(target, key, value, receiver);
	// 3. 拦截 set ,执行列表中的函数
	queueObservers.forEach((observe) => observe());
	return result;
}

// 调用
const person = observable({
	name: "张三",
	age: 20
});

function print() {
	console.log(`${person.name}, ${person.age}`);
}

observe(print);
person.name = "李四";// 李四,20
person.age = 21;// 李四,21

let me try

反转字符串

// 解法一: 将字符串转为数组,利用Array.prototype.reverse反转数组,再合并数组为字符串
function reverseString(str) {
  return str.split('').reverse().join('')
}

reverseString("hello");

// 解法二:新键一个字符串,逆序循环读取字符串,并赋值给新字符串
function reverseString(str) {
  var reversedStr = "",
  for (i = str.length - 1; i >= 0; i--) {
    reversedStr += str[i];
  }
  return reversedStr;
}

理解Function.prototype.apply.call(fn,thisArg,args),为何要这样用

学习阮一峰reflect中的一个疑惑?

对老写法的不解

// 老写法
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1

// 新写法
Reflect.apply(Math.floor, undefined, [1.75]) // 1

前置知识

fn.call(thisArg, arg1, arg2, ...) 
// 等价于
fn.apply(thisArg, [arg1, arg2, ...])
// 等价于
thisArg.fn(arg1, arg2, ...)

将 Function.prototype.apply 看做是一个整体

Function.prototype.apply.call(fn, thisArg, args)
// 等价于
// fn.(Function.prototype.apply)(thisArg, args)
fn.apply(thisArg, args)

那么,之前的问题也就可以等价于

Math.floor.apply(undefined, [1.75])

结论

Function.prototype.apply.call(fn, thisArg, args1, args2, ...) 等价于 fn.apply(thisArg, args)

问题来了,为什么不直接使用 fn.apply(thisArg, [...args]),而要这么麻烦的写法呢?

  • 兼容 ie 等其他浏览器

github.com/facebook/re…

参考资料:

理解Function.prototype.apply.call(fn, this Arg, args)