diff --git a/docs/article/assets/Object.prototype.toString.png b/docs/article/assets/Object.prototype.toString.png new file mode 100644 index 00000000..aed340f0 Binary files /dev/null and b/docs/article/assets/Object.prototype.toString.png differ diff --git a/docs/article/assets/vm-demo.png b/docs/article/assets/vm-demo.png new file mode 100644 index 00000000..bab33e33 Binary files /dev/null and b/docs/article/assets/vm-demo.png differ diff --git a/docs/article/【字节跳动】前端面试题总结.md b/docs/article/【字节跳动】前端面试题总结.md new file mode 100644 index 00000000..0684f827 --- /dev/null +++ b/docs/article/【字节跳动】前端面试题总结.md @@ -0,0 +1,137 @@ +# 【字节跳动】前端面试题总结 + +## 看代码说结果 + +### 代码输出结果1 + +```js +console.log(['1', '2', '3'].map(parseInt)) +``` + +``` +[1, NaN, NaN] +``` + +### 代码输出结果2 + +```js +let [a = 1, b] = [] +console.log(a, b) +``` + +``` +1 undefined +``` + +### 代码输出结果3 + +```js +Promise.resolve() + .then(() => { + Promise.resolve() + .then(() => { + console.log(1) + }) + .then(() => { + console.log(2) + }) + }) + .then(() => { + console.log(3) + }) +``` + +``` +1 3 2 +``` + +### 代码输出结果4 + +```js +const obj = { 3: '3', 2: 2, 1: '1', name: 'name', age: 'age' } +console.log(Object.keys(obj)) +``` + +``` +['1', '2', '3', 'name', 'age'] +``` + +### 代码输出结果5 + +```js +let myArray = {} +myArray['0'] = 'a' +myArray['1'] = 'b' +myArray.length = 2 +console.log(...myArray) +``` + +``` +``` + +### 代码输出结果6 + +```js +console.log([1, 2, 3, 4, 5].splice(1, 2, 3, 4, 5)) +console.log([1, 2, 3, 4, 5].slice(1, 2, 3, 4, 5)) +``` + +``` +[2, 3] +[2] +``` + +### 代码输出结果7 + +```js +console.log([].constructor === Array) +console.log(typeof [] === 'array') +console.log(typeof null === 'object') +console.log('' instanceof Object) +``` + +``` +true +false +true +false +``` + +## 问答题 + +### HTTP缓存的请求头与响应头有哪些 + +- 强制缓存 + - 响应头: Expires 与 Cache-Control + - Expires 绝对时间 + - Cache-Control 相对时间 +- 协商缓存 + - 响应头: Etag 请求头: If-Non-Match + - 响应头: Last-Modified 请求头: If-Modified-Since + +### 宏任务与微任务有哪些区别?简述他们的应用场景 + +宏任务与微任务优先级不同 + +- 先执行同步代码,后执行异步代码 +- 主线程代码执行完毕后,检查微任务队列是否为空,非空则优先执行微任务 +- 每次执行宏任务之前,都会检查微任务队列是否为空,非空则优先执行微任务 + +常见的宏任务与微任务 + +- 宏任务 `script` `setTimeout` `setInterval` `postMessage` `MessageChannel` `setImmediate (NodeJS)` +- 微任务 `Promise.then` `Object.observe` `MutationObserver` `process.nextTick (NodeJS)` + +## 算法 + +中文数字转为阿拉伯数字 + + +给定一个字符串,返回该字符串的所有组合 + +```js +输入 abc +输出 ['abc', 'acb', 'bac', 'bca', 'cab', 'cba'] +``` + +回溯算法 \ No newline at end of file diff --git a/docs/article/【快手】深入理解前端面试题.md b/docs/article/【快手】深入理解前端面试题.md new file mode 100644 index 00000000..15d275d4 --- /dev/null +++ b/docs/article/【快手】深入理解前端面试题.md @@ -0,0 +1,447 @@ +# 【快手】深入理解前端面试题 + +## 快手一面 + +### Vue生命周期 + +`beforeCreate created beforeMount mounted` + +`beforeUpdate updated` + +`beforeDestory destoryed` + +Vue3移除了`beforeCreate` `created`两个声明周期钩子,这是因为setup发生在开始创建组件之前,在`beforeCreate`和`created`之前执行 + +可以在setup中使用的生命周期函数:`onMounted` `onUpdated` `onUnmounted` `onBeforeUpdate`这几个, + +![Vue 生命周期](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a299bd2229fb45d68a1cdd8731c94449~tplv-k3u1fbpfcp-zoom-1.image) + +### 网络请求一般在什么时候发起,为什么 + +越早越好,一般是放在`created`或`onMounted`或者`setup`中 + +- `created`(vue2) 此时组件内的基本数据已经创建好,组件的模板结构尚未生成 +- `mounted`(vue2) `onMounted`(vue3) 组件挂载到DOM树上,可以获取到DOM +- `setup`(vue3) 时机要早于`beforeCreated`和`created` 所以在setup中发起网络请求也可以 + +### setup的执行时机相当于哪个生命周期 + +`setup`的执行要早于`beforeCreated`和`created`,可以认为相当于这两个生命周期 + +### Vue2和Vue3的响应式原理 + +#### Vue2响应式原理 + +全部使用`Object.defineProperty()`中的set与get函数 + +#### Vue3响应式原理 + +`ref`使用的是`Object.defineProperty()`,而`reactive`使用的是`Proxy` + +`Proxy`可以直接深度代理一个对象,通过设置handler中的捕获器可以对对象创建一个代理,将各种行为监听并且同步到对象本身上 + +```js +const obj = { + name: 'Ziu', + age: 18 +} +const proxy = new Proxy(obj, { + set: function (target, key, newVal) { + console.log(`监听: ${key} 设置 ${newVal}`) + target[key] = newVal + }, + get: function (target, key) { + console.log(`监听: ${key} 获取`) + return target[key] + } +}) +``` + +### Proxy相比于defineProperty有何优势 + +[defineProperty 和 Proxy区别](https://segmentfault.com/a/1190000041084082) + +1. **监听数据的角度** + + 1. `defineproperty`只能监听某个属性而不能监听整个对象。 + 2. `proxy`不用设置具体属性,直接监听整个对象。 + 3. `defineproperty`监听需要知道是哪个对象的哪个属性,而`proxy`只需要知道哪个对象就可以了。也就是会省去`for in`循环提高了效率。 + +2. **监听对原对象的影响** + + 1. 因为`defineproperty`是通过在原对象身上新增或修改属性增加描述符的方式实现的监听效果,一定会修改原数据。 + 2. 而`proxy`只是原对象的代理,`proxy`会返回一个代理对象不会在原对象上进行改动,对原数据无污染。 + +3. **实现对数组的监听** + + 1. 因为数组 `length` 的特殊性 `(length 的描述符configurable 和 enumerable 为 false,并且妄图修改 configurable 为 True 的话 js 会直接报错:VM305:1 Uncaught TypeError: Cannot redefine property: length)` + 2. `defineproperty`无法监听数组长度变化, `Vue`只能通过重写数组方法的方式变现达成监听的效果,光重写数组方法还是不能解决修改数组下标时监听的问题,只能再使用自定义的`$set`的方式 + 3. 而`proxy`因为自身特性,是创建新的代理对象而不是在原数据身上监听属性,对代理对象进行操作时,所有的操作都会被捕捉,包括数组的方法和`length`操作,再不需要重写数组方法和自定义`set`函数了。(代码示例在下方) + + **4. 监听的范围** + + 1. `defineproperty`只能监听到`value`的 `get set` 变化。 + 2. `proxy`可以监听除 `[[getOwnPropertyNames]]` 以外所有`JS`的对象操作。监听的范围更大更全面。 + +#### Proxy优势 + +- Proxy可以直接监听整个对象而非属性。 +- Proxy可以直接监听数组的变化。 +- Proxy有13中拦截方法,如`ownKeys、deleteProperty、has` 等是 `Object.defineProperty` 不具备的。 +- Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而`Object.defineProperty`只能遍历对象属性直接修改; +- Proxy做为新标准将受到浏览器产商重点持续的性能优化,也就是传说中的新标准的性能红利。 + +#### Object.defineProperty优势 + +兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平。 + +#### Object.defineProperty劣势 + +- `Object.defineProperty` 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。 +- `Object.defineProperty`不能监听数组。是通过重写数据的那7个可以改变数据的方法来对数组进行监听的。 +- `Object.defineProperty` 也不能对 `es6` 新产生的 `Map`,`Set` 这些数据结构做出监听。 +- `Object.defineProperty`也不能监听新增和删除操作,通过 `Vue.set()`和 `Vue.delete`来实现响应式的。 + +### Vue3数据双向绑定原理 + +### ref与reactive区别与适用场景 + +- ref接受的数据类型:基本类型(string number bigint boolean undefined symbol null),引用类型 + - 把参数加工成一个响应式对象,全称为reference对象(简称为ref对象) + - 如果参数是基本类型,那么形成响应式依赖于`Object.defineProperty()`的get()和set() + - 如果ref的参数是引用类型,底层ref会借助reactive的Proxy生成代理对象,从外部依然是通过`.value`获取 +- reactive只接受引用类型(Object Array) + - 它的响应式是更深层次的,底层是将数据包装成一个Proxy + - 参数必须是对象或者数组,如果要让对象的某个元素实现响应式时比较麻烦。需要使用toRefs + - 必须只使用那些不会改变引用地址的操作,否则会丢失响应性,如解构、重新赋值 + +```js +const obj1 = ref({ name: 'Ziu', age: 18 }) +console.log(obj1) // RefImpl +console.log(obj1.value) // Proxy + +const obj2 = reactive({ + name: 'Kobe', + age: 19 +}) +console.log(obj2) // Proxy +``` + +### Vue3功能上相比于Vue2有哪些优点 + +- Composition API + - 生命周期钩子 + - `ref() reactive()` + - hook复用代码 +- 不再需要根标签 (Fragments) +- 性能提升 diff算法 +- Proxy与defineProperty + +[Vue3 对比 Vue2.x 差异性、注意点、整体梳理,与React hook比又如何?](https://juejin.cn/post/6892295955844956167) + +### Vue组件传参方法 + +- prop & emit +- provide & inject +- 子组件defineExpose 父组件ref获取 + +### Vuex异步操作如何同时修改多个state + +### ES6特性了解哪些 + +- let & const + - 独立作用域 +- 箭头函数 + - 无this 无arguments对象 +- Promise + - 类方法、实例方法 +- async await + - 异步函数 生成器语法糖 + +### let & const的特性 + +不存在变量提升 暂时性死区 + +> ES6 明确规定,如果区块中存在`let`和`const`命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。 + +```js +var tmp = 123; + +if (true) { + tmp = 'abc'; + let tmp; +} +console.log('tmp =', tmp); +``` + +### Promise介绍一下 + +- 三种状态 `pending fulfilled rejected` +- 类方法 `.resolve .reject .all .race .any` +- 实例方法 `.then .catch .finally .allSettled` + - `.then`方法两个入参 一个是上一个Promise对象resolved的回调 另一个是rejected的回调,返回一个新的Promise对象 + - `.then`传入的回调函数会被加入微任务队列 + - `.catch`本质上是只有错误处理函数的`.then` + +### Promise.all .race .any功能及区别 + +传入的都是一个可迭代对象(iterable),迭代器,类似Array + +- Promise.all() 所有都resolved 将结果按顺序放入数组中返回 如果某个rejected则直接rejected,原因为第一个rejected的原因 +- Promise.race() 一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝 +- Promise.any() any方法会等到一个fulfilled状态,才会决定新的Promise状态 如果所有Promise都是reject的,那么也会等到所有Promise都变成rejected状态 + +### Promise看代码写结果 + +```js +function testPromise() { + return new Promise((resolve, reject) => { + console.log('1'); + resolve(2); + reject(3); + console.log('4'); + }).then(res => { + console.log('res = ', res); + }, err => { + console.log('err =', err); + }); +} + +testPromise(); +``` + +输出结果:`1 4 res = 2` + +执行`resolve`之后,当前Promise的状态变为`fulfilled`不再改变,即使后面继续调用了`reject`,后续代码继续执行输出`'4'`,随后执行微任务中的`.then`,输出`res = 2` + +### 手写Promise.all() + +```js + function selfPromiseAll(iterable) { + return new Promise((resolve, reject) => { + const promises = Array.from(iterable) // 将可迭代对象转为数组 + const result = [] // 保存结果 + let count = 0 // 记录是否所有promise都执行完毕 + // 并发执行每一个promise + for (let i = 0; i < promises.length; i++) { + Promise.resolve(promises[i]) + .then((res) => { + result[i] = res // 将结果按顺序存入result + count++ + // 保证每一个Promise都执行完毕后再resolve + if (count === promises.length) { + resolve(result) + } + }) + .catch((err) => reject(err)) // 只要有reject 外部Promise直接reject + } + }) + } +``` + +### Cookie localStorage SessionStorage区别及使用场景 + +| | cookie | localStorage | sessionStorage | +| --- | --- | --- | --- | +| 大小 | 4Kb | 5MB | 5MB | +| 兼容 | H4/H5 | H5 | H5 | +| 访问 | 任何窗口 | 任何窗口 | 同一窗口 | +| 有效期 | 手动设置 | 无 | 窗口关闭 | +| 存储位置 | 浏览器和服务器 | 浏览器 | 浏览器 | +| 与请求一起发送 | 是 | 否 | 否 | +| 语法 | 复杂 | 简单 | 简单 | + +- Cookie是由`?key1=value1;key2=value2`组成的,可以通过`encodeURIComponent()`来保证它不包含任何逗号、分号或空格(cookie值中禁止使用这些值). +- Cookie一般的字段有`path domain max-age expires secure` +- 不同的host之间的localStorage、sessionStorage对象是隔离的 + +### localStorage常用方法 + +```js +localStorage.setItem('key', value) +localStorage.getItem('key') +localStorage.removeItem('key') +localStorage.clear() +``` + +### 跨域解决方法 + +- JSONP +- CORS +- 代理服务器 + +#### JSONP + +根据同源策略的限制,在**端口,域名,协议**这三者**一个或者多个不同的情况下**,就会出现跨域限制,请求发送到了服务器并且服务器也响应了数据,但是浏览器不会为你展示出来。 + +但是,` + +``` + +` +``` + +### Proxy与defineProperty实现数据劫持 + +[深入理解Proxy与Reflect](https://juejin.cn/post/7179634726309724219) + +### 算法: 两数之和-修改版 + +从数组中找到两数之和为目标值的数字对数 不允许重复使用数字 + +- `[1, 2, 3, 4, 4], 5` 结果为 `2` +- `[1, 1, 2, 3, 4, 4], 5` 结果为 `3` + +```js +/** + * 从数组中找到两数之和为目标值的数字对数 不允许重复使用数字 + * @param {number[]} nums 数组 + * @param {number} target 目标值 + * @returns {number} 次数 + */ + +function twoSum(nums, target) { + // 边界情况 + if (nums.length < 2) return 0 + + let count = 0 + const map = new Map() + + for (let i = 0; i < nums.length; i++) { + const num = nums[i] + const diff = target - num + const value = map.get(diff) // undefined | number + + // 如果找到了与diff相等的num + if (value !== undefined && value > 0) { + count++ + map.set(diff, value - 1) // 剩余数字个数-1 + } else { + // 未找到 则将num设为键 值为出现次数 + const count = map.get(num) === undefined ? 1 : map.get(num) + 1 + map.set(num, count) + } + } + + return count +} + +console.log(twoSum([1, 2, 3, 4, 4], 5)) // 2 +console.log(twoSum([1, 1, 2, 3, 4, 4], 5)) // 3 +``` diff --git a/docs/article/【用友金融】前端面试题总结.md b/docs/article/【用友金融】前端面试题总结.md new file mode 100644 index 00000000..3415969c --- /dev/null +++ b/docs/article/【用友金融】前端面试题总结.md @@ -0,0 +1,257 @@ +# 【用友金融】前端面试题总结 + +## 回流与重绘 + +下列关于回流和重绘的说法错误的是 + +- 回流的性能开销大于重绘的性能开销 +- 当页面结构、尺寸等改变时会发生回流 +- 回流一定会引起重绘,重绘也一定会引起回流(x) +- 当页面结构不改变只是样式发生改变时会发生重绘,例如背景颜色改变时会发生重绘 + + +- `display: none` 指的是元素完全不陈列出来,不占据空间,涉及到了DOM结构,故产生reflow与repaint +- `visibility: hidden` 指的是元素不可见但存在,保留空间,不影响结构,故只产生repaint,但不可触发绑定事件 +- `opacity: 0` 指的是元素不可见但存在,保留空间,不影响结构,并且,如果该元素已经绑定一些事件,如click事件,那么点击该区域,也能触发点击事件的 + +## CSS属性 + +下列选项中哪个描述对于visibility: hidden;与display: none;是正确的 + +- visibility属性不可继承 +- visibility: hidden; 不占据页面空间 +- display: none; 不占据页面空间(√) +- 都无法通过DOM交互 + +## 函数执行结果 + +### 题目1 + +```js +(function () { + var a = (b = 5); +})(); + +console.log(b); +console.log(a); +``` + +结果为: + +``` +5 +Error +``` + +### 题目2 + +```js +console.log(1 + '2') +console.log(1 - '2') +``` + +结果为: + +```js +12 +-1 +``` + +### 题目3 + +```js +var a = 1 + +setTimeout(function () { + a = 2 + console.log(a) +}, 0) + +var a +console.log(a) + +a = 3 +console.log(a) +``` + +结果为: + +``` +1 +3 +2 +``` + +### 题目4 + +```js +function f() { + return f +} + +console.log(new f() instanceof f) +``` + +结果为: + +``` +false +``` + +### 题目5 + +```js +var foo = { + bar: function () { + return this.baz + }, + baz: 1 +} + +console.log(typeof (f = foo.bar)()) +``` + +结果为: + +``` +undefined +``` + +### 题目6 + +```js +var a = (b = 1, c = 2) +console.log(a, b, c) +``` + +结果为: + +``` +2 1 2 +``` + +### 题目7 + +```js +var company = { + address: 'beijing' +} + +var jjworld = Object.create(company) +delete jjworld.address +console.log(jjworld.address) +``` + +结果为: + +``` +beijing +``` + +### 题目8 + +```js +function side(arr) { + arr[0] = arr[2] +} + +function a(a, b, c = 3) { + c = 10 + side(arguments) + return a + b + c +} + +console.log(a(1, 1, 1)) +``` + +结果为: + +``` +12 +``` + +## CSS权重的优先级 + +内联样式 > ID选择器 > 类选择器 > 标签选择器 > 通配符 + +## HTTP状态码 + +越多越好 + +- 400 临时重定向 +- 500 服务器内部错误 +- 404 服务器无法找到对应资源 +- 200 请求正常处理 + +## BFC(块级格式上下文) + +下列选项对产生BFC描述错误的是: + +- overflow为visible(x) +- position为absolute +- display为table-cell +- float属性不为none + +## 元素浮动 + +设置元素浮动后,元素的display值哪个是正确的? + +- inline-block +- float +- inline +- block(√) + +## HTTP请求方法 + +下面哪个选项不是HTTP的请求方法 + +- HEAD +- PUT +- OPTIONS +- PUSH(x) + +HTTP请求方法有: + +`GET POST DELETE PUT OPTIONS` +`CONNECT HEAD PATCH TRACE` + +## 跨域问题 + +通常有哪些方法解决跨域问题? + +- 利用JSONP +- 反向代理 +- 服务器配置CORS + +## 了解Webpack的哪些配置项 + +- `resolve.alias` 为路径设置别名 +- `resolve.extensionAlias` 后缀省略时 按此顺序指定的后缀名解析文件 +- `module.rules` 包含`test` `loader` `option` 正则匹配后缀名 使用指定的loader解析文件 + +## 判断变量类型 + +直接使用`typeof`无法判断引用类型变量的类型 + +```js +console.log(typeof []) // object +console.log(typeof {}) // object +console.log(typeof null) // object +``` + +## JS浮点数相加精度问题 + +## 简单讲讲Vue Router原理 + +## ES6熟悉吗 + +[ES6教程](https://es6.ruanyifeng.com/) + +- `let` `const` +- 解构赋值 扩展运算符 可选链 +- `Promise` `Async` +- `Class` `Set` `Map` `Symbol` `Proxy` + +## CSS如何实现水平居中与垂直居中 + +参见[CSS学习笔记](../note/CSS.md) \ No newline at end of file diff --git a/docs/article/浅析defineProperty与Proxy实现的双向绑定.md b/docs/article/浅析defineProperty与Proxy实现的双向绑定.md new file mode 100644 index 00000000..cdf0b0ab --- /dev/null +++ b/docs/article/浅析defineProperty与Proxy实现的双向绑定.md @@ -0,0 +1,64 @@ +# 浅析defineProperty与Proxy实现的双向绑定 + +> 文章内容总结自Vue官网 [深入响应式原理](https://cn.vuejs.org/v2/guide/reactivity.html#%E5%A6%82%E4%BD%95%E8%BF%BD%E8%B8%AA%E5%8F%98%E5%8C%96) + +## 🔰 Vue2的响应式原理 + +![image.png](https://v2.cn.vuejs.org/images/data.png) + +> 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 `data` 选项,Vue 将遍历此对象所有的 property,并使用 [`Object.defineProperty`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) 把这些 property 全部转为 [getter/setter](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Working_with_Objects#%E5%AE%9A%E4%B9%89_getters_%E4%B8%8E_setters)。 +> +> 每个组件实例都对应一个 **watcher** 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。 + +Vue2的响应式原理,利用的是 `Object.defineProperty()` 的 `setter` 属性: + +`defineProperty()` 方法用于**精确**定义一个对象的属性,能够指定属性的各种特征,其中的 `set` 属性能够为对象指定一个 `setter` 函数,每次该属性的值发生修改,就会调用此函数。 + +> 更多可以配置的属性请参看:[什么是对象的数据属性描述符?存储属性描述符?](https://juejin.cn/post/7088335075061792782) + +这也是Vue2实现响应式数据、数据双向绑定的原理。 + +可以使用此方法实现一个简单的数据双向绑定的Demo: + +![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f241135dbeb04e829fe6897c2e418aa2~tplv-k3u1fbpfcp-watermark.image?) + +* 输入框内的内容改变,`.vBox` 展示的文本会随之改变。 +* 点击按钮修改 `vm.text`,输入框内的值和 `.vBox` 的文本都会发生改变。 + +```html + + + +
+ + +``` + +通过 `defineProperty` 实现的响应式,**不能检测**数组和对象的变化: + +**对于对象:** + +Vue 无法检测 property 的添加或移除。 + +var vm = new Vue({ data:{ a:1 } }) // `vm.a` 是响应式的 vm.b = 2 // `vm.b` 是非响应式的 + +**对于数组:** + +1. 当你利用索引直接设置一个数组项时,例如:`vm.items[indexOfItem] = newValue` +2. 当你修改数组的长度时,例如:`vm.items.length = newLength` + +## 🔰 Vue3的响应式原理 diff --git a/docs/article/深入JavaScript数据类型.md b/docs/article/深入JavaScript数据类型.md new file mode 100644 index 00000000..2e4809d9 --- /dev/null +++ b/docs/article/深入JavaScript数据类型.md @@ -0,0 +1,159 @@ +## 深入JavaScript数据类型 + +JavaScript包含以下几种数据类型: + +- Number 数字 +- String 字符串 +- Boolean 布尔值 +- Symbol 符号 (ES6新增) +- Object 对象 + - Function 函数 + - Array 数组 + - Date 日期 + - RegExp 正则表达式 + - ... +- null 空 +- undefined 未定义 + +从语言底层值的可变与不可变,可以将JS中的数据分为两种:不可变值(原始类型)和可变值(引用类型) + +除了Object及继承自Object的特殊对象,其他的类型都为**原始类型**。 + +## typeof运算符 + +除了null,所有**原始类型**都可以通过`typeof`运算符得到不同的结果 + +而null与object通过`typeof`运算符得到的结果都为`'object'` + +```javascript +// 除了 null 其他原始类型的变量都可以通过 typeof 得到其类型 +// 而 null 与 object 通过 typeof 运算得到的都是 'object' +const targets = [18, 'Ziu', true, Symbol(''), {}, null, undefined] + +for (const t of targets) { + console.log(typeof t) // number string boolean symbol object object undefined +} +``` + +### `typeof null === 'object'` + +这里援引MDN的解释: + +> 在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。 +> +> 由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返回 "object"。 + +### 关于new操作符 + +所有使用 new 操作符调用的构造函数,都将返回非基本类型(object 或 function) + +- 大多数返回的是一个对象,即 `object` +- 而构造函数Function返回的是 `function` + +```javascript +// 针对普通构造函数 +const str = new String('Ziu') +const num = new Number(100) + +console.log(typeof str) // 'object' +console.log(typeof num) // 'object' + +// 针对 Function 构造函数 +const fun = new Function() +console.log(typeof fun) // 'function' +``` + +### 字符串原始值和字符串对象 + +typeof 操作符区分 `String` 对象和原始字符串值: + +```javascript +const s1 = '' +const s2 = new String('') +const s3 = String('') + +console.log(s1 instanceof String) // false +console.log(s2 instanceof String) // true +console.log(s3 instanceof String) // false +``` + +通过new操作符创建的是一个对象,它将被添加到原型链上(详见new一个对象时发生了什么) + +而直接调用 String 函数,返回的是一个字符串原始值,本质上 `s3` 和 `s1` 是相同的 + +而`s1`变量自创建之初,就是原始类型,没有挂载到原型链上,自然也就无法通过`instanceof`检查一个对象 + +而之所以能够在`s1`上调用字符串的方法,是因为**包装类型**(见下文) + +> instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。 +> +> 在本例中,instanceof用于检查s1变量的原型链上,是否包含构造函数String的显式原型(String.prototype),即:s1是否由String创建 + +## 包装类型 + +除了null和undefined,所有**原始类型**都有其相应的**对象包装类型**,例如`18`的对象包装类型是`Number`,而`'Ziu'`的对象包装类是`String` + +这为处理原始值提供可用的方法,例如,`Number`对象提供了`toFixed()`这样的方法。 + +当我们在原始值上访问属性时,JavaScript会**自动将值包装到相应的包装对象中**,并访问对象上的属性: + +```javascript +console.log((18.8).toFixed()) // '19' +console.log('Ziu'.toUpperCase()) // 'ZIU' +``` + +## 使用toString检查对象类型 + +由Object派生的每个特殊对象类型都有 `toString` 方法,而且他们都被不同程度的改写: + +```javascript +// 注意,不应当在null或undefined上调用任何方法,这将抛出错误,因为他们没有对应任何包装类型 +const targets = [18, 'Ziu', true, Symbol(''), {}, new Date()] + +for (const t of targets) { + console.log(t.toString()) // '18' 'Ziu' 'true' 'Symbol()' '[object Object]' Sat Feb 18 2023 20:49:31 GMT+0800 (GMT+08:00) +} +``` + +这个 `toString` 方法定义在 Object.prototype 上,被其他特殊对象类型所继承。 + +- 如果我们直接调用 `Object.prototype.toString()` 那么得到的是 `[object Object]` 因为这是在Object上调用得到的结果 +- 如果我们在各自不同的对象上调用 `toString` ,得到的是不同对象改写后的结果 + +因为Object是所有子类的父类,所以任何类型的对象都可以通过this绑定的方式,调用`Object.prototype.toString()`方法,返回该对象的类型的字符串表示 + +这也是大多数情况下判断对象类型的方法: + +```js +function classof(target) { + const res = Object.prototype.toString.call(target) // [object xxx] + return res.slice(8, -1) +} + +console.log(classof({})) // Object +``` + +### 核心原理 + +[深入Object.prototype.toString](https://juejin.cn/post/6844903477940846600) + +参考ECMA6规范文档:[Object.prototype.toString()](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.prototype.tostring) + +#### ES5标准下 `Object.prototype.toString` 执行原理 + +- 如果 `this` 是 `undefined` + - 返回 `[object Undefined]` +- 如果 `this` 是 `null ` + - 返回 `[object Null]` +- 令 `O` 为以 `this` 作为参数调用 `ToObject` 的结果 +- 令 `class` 为 `O` 的内部属性 `[[Class]]` 的值 +- 返回三个字符串 `"[object"` `class` 以及 `"]"` 拼接而成的字符串。 + +#### ES6标准下 `Object.prototype.toString` 执行原理 + +`Object.prototype.toString()`被调用时,会进行如下步骤: + +![Object.prototype.toString()](./assets/Object.prototype.toString.png) + +在ES6里,之前的内部属性 `[[Class]]` 不再使用,取而代之的是一系列的 internal slot +