### 原始类型
1. js原始类型有哪几种?null是对象吗?
* 6中原始类型(undefined,null,boolean,string,number,symbol)
* 原始类型存储的都是值,没有函数可以调用。
~~~
**undefined.toString()//报错**
"1".toString()是可以使用的,”1“不是原始类型了,被强制转换成String类型(对象类型)
console.log(0.1 + 0.2 !== 0.3) //true
~~~
* null不是对象类型, 虽然 `typeof null =》object`
symbol:ES6引入,表示独一无二的值,通过Symbol函数生成,Symbol函数前不能使用new命令,否则会报错,因为生成的Symbol是一个原始类型,不是对象(不能添加属性),是一种类似字符串的数据类型。
Symbol值可以作为标识符,用于对象的属性名,保证不会出现同名的属性。
~~~
const shapeType = {
triangle: Symbol()
};
Object.getOwnPropertySymbols
~~~
### 对象类型
1. 对象类型和原始类型的不同?函数参数是对象发生的问题?
* 不同:原始类型存储的是值,对象类型存储的是地址(指针),创建一个对象类型时,计算机会在内存中开辟一个空间来存放值,这个空间拥有一个地址(指针)
题型:
~~~
function test(person){
person.age = 26;
person = {
name:'yyy',
age:30
}
return person
}
const p1 ={ name:'xxx', age: 1}
const p2 = test( p1 );
console.log(p1);//{name:'xxx',age:26}
console.log(p2) //{name:'yyy',age:30}
~~~
* 函数传参是传递**对象指针**的副本(地址)
### typeof vs instanceof vs toString
1. typeof是否能正确判断类型?instanceof能正确判断对象的原理是什么?
* typeof对原始类型来说,除了null都可以显示正确的类型
~~~
typeof 1 //number
typeof "1" //string
typeof undefined //undefined
typeof true //boolean
typeof Symbol() //symbol
~~~
* typeof对于对象,除了函数(function)都会显示object
* typeof不能准确判断变量到底是什么类型
* 判断对象的正确类型,使用instanceof(原始类型不鞥呢通过instanceof直接判断)
~~~
const Person = function(){}
const p1 = new Person();
p1 instancof Person //true
var str = 'hello world';
str instanceof String //false
var str = new String('xxx');
str1.instanceof String //true
~~~
* 补充 instanceof判断原始类型(instanceof 也不是百分百可信的)
~~~
class PrimitiveString {
static [Symbol.hasInstance](x)
{ return typeof x === 'string' }
}
console.log('hello world' instanceof PrimitiveString) // true
~~~
* 判断利器: **Object.prototype.toString.call**(对象的原生扩展函数,精确区分数据类型)
~~~
var getType = Object.prototype.toString
getType.call('aaa') //[object String]
getType.call(222) //[object Number]
getType.call(true) //[object Boolean]
getType.call(undefined) //[object Undefined]
getType.call(null) //[object Null]
getType.call({}) //[object Object]
getType.call([]) //[object Array]
getType.call(function(){}) //[object Function]
~~~
*
### 类型转换
* 三种情况(转为布尔,转为数字,转为字符串)
* 常见转换,
~~~
0,-0,NaN,undefined,null =>Boolean(false)
Boolean,函数,Symbol=》String('true')
数组([1,2])=>String( '1,2' )
对象 =》String([object Object])
字符串=》数字('1'=>1 'a'=>NaN)
数组=》数字(空数组为0,存在一个元素且为数字转数字,其他情况NaN)
null = 》数字(0)( 但是null != 0)
除了数组的引用类型 =》 数字(NaN)
Symbol = 》数字(抛错)
~~~
* 转Boolean(undefined,null,false,NaN,'',0,-0=》 false)
* 对象转原始类型
#### 四则运算
1. 加法运算规则
* 有一方为字符串,会把另一方转为字符串
* 如果一方不是字符串或数字,那么会将它转为数字或字符串
~~~
true +true //2 //都不是字符串,转为数字
true + "x" //truex
4+[1,2,3] //41,2,3 //都不是字符串,后者只能转为字符串
'a' + +'b' //'aNaN' (+'b'=>NaN)
~~~
* 除了加法运算,只有其中一方是数字,那么另一方就会被转为数字
~~~
4 * '3' //12
4*[] //0
4*[1,2] //NaN
~~~
#### 比较运算
* 如果是对象,就通过 toPrimitive转对象
toPrimitive:将对象转成原始值,如果是原始值,直接返回;否则调用valueOf()(求值),如果结果是原始值,返回;否则调用toString()(求字符串),如果是原始值,返回;否则抛错
* 如果是字符串,通过unicode字符索引来比较
* valueOf() toString()
### this
* 如何正确判断this?箭头函数的this是什么?
~~~
function foo(){
console.log(this.a);
}
var a =1;
foo(); // this指 window 1
const obj = {
a: 2,
foo: foo
}
obj.foo(); // this 指obj 2
const c = new foo(); //c undefined
c.foo()
~~~
分析:
* 直接调用foo,this指向window
* obj.foo():谁调用了函数,谁就是this
* new方式,this被永远绑定在c上
箭头函数的this
~~~
function a(){
console.log(this);
return ()=>{
console.log(this);
return ()=>{
console.log(this)
}
}
}
console.log(a()()) //window window window
~~~
* 首先,箭头函数其实是没有this的,箭头函数中的this值取决于包裹箭头函数的第一个普通函数的this。包裹箭头函数的第一个普通函数是a,所以this是window。另外,**对箭头函数使用bind这类函数是无效的**。
**改变上下文的API:** bind,call,apply
* 对于这些函数,this取决于第一个参数,如果第一个参数为空,this指向window
* 多次bind,不管我们给函数bind几次,fn中的this永远有第一次bind决定
~~~
let a = {};
let fn = function(){ console.log(this)}
fn.bind().bind(a); //window
~~~
等于
~~~
// fn.bind().bind(a) 等于
let fn2 = function fn1() {
return function() {
return fn.apply()
}.apply(a)
}
fn2()
~~~
* 多个规则同时出现,根据优先级来决定this指向哪里
n**ew的方式优先级最高,接下来是bind,然后是obj.foo(),最后是foo这种调用方式,同时,箭头函数this一旦被绑定,就不会被任何方式改变**。
![](https://box.kancloud.cn/9061aa32ca1fd1e4b3e8476c5e595aae_651x476.png)
### == vs ===(有什么区别?)
==:如果双方的类型不一样的话,就会进行**类型转换**
=== :判断类型和值是否都相同
判断流程:
1. 判断两者类型是否相同。相同比大小
2. 类型不同,进行类型转换
3. 先判断是否对比 **null和undefined**,是的话就会返回true
4. 判断两者类型是否为**string和number,是的话将字符串转为number**
5. 判断其中一方为**boolean,是的话把boolean转为number**(true=>1)
6. 判断其中一方是否为object,且另一方为string、number或者symbol,是的话就把object转为原始类型再进行判断
~~~
'1' == {name:'xxx'}
转为:
'1' == '[object Object]'
~~~
7. `[] == ![] `
分析:
~~~
[] == ![]
转为: [] == false
一方为boolean类型,把boolean类型转为 number =》
[] == 0;//一个操作数是对象,另一个不是,则调用对象的(valueOf toString)方法
转为 '' == 0
因此为true!!!
~~~
~~~
{} == !{}
!{} =》 false
=》{} == false
=》 {} == 0
=> 'object'
{} == {} //同为对象类型,肯定不等
~~~
![](https://box.kancloud.cn/a7f1f2ac796ad63a0e855096f48d3b91_692x297.png)
### 闭包
定义:函数A内部有一个函数B,函数B可以访问到函数A中的变量,那么函数B就是闭包
不完整定义:函数嵌套函数,然后返回一个函数,下面这个例子可以反驳
~~~
function A(){
let a = 1;
window.B = function(){
console.log(a);
}
}
A();
B();// 1
~~~
经典面试题,解决var定义函数的问题
~~~
for(var i = 1; i<=5; i++){
setTimeout(function timer(){
console.log(i)
}, i*1000)
}
// 5 5 5 5 5
~~~
setTimeout是异步函数,先把循环全部执行完毕,这是` i是6`,输出一堆6
解决办法有三种
1. 闭包的形式
~~~
for(var i = 1; i<=5; i++){
(function(j){
setTimeout(function timer(){
console.log(j)
}, j*1000)
})(i)
}
// 1 2 3 4 5
~~~
首先使用立即执行函数,将i传入函数内部,这个时候就被固定在参数j上面不会改变
2. 使用setTimeout的第三个参数,这个参数会被当成timer函数的参数传入
~~~
for(var i = 1; i<=5; i++){
setTimeout(function timer( j ){
console.log(j)
}, i*1000 ,i )
}
~~~
3. 使用let定义i,推荐
~~~
for(let i = 1; i<=5; i++){
setTimeout(function timer(){
console.log(i)
}, i*1000)
}
~~~
### 深浅拷贝
**题目:什么是浅拷贝?如何实现浅拷贝?什么是深拷贝?如何实现深拷贝?**
对象类型在赋值过程中,其实是复制了地址,改变一方其他都被改变。
1. 浅拷贝
* `Object.assign`:只会拷贝所有的属性值到新的对象,如果属性值是对象的话,拷贝的是地址,所有并不是深拷贝
~~~
let a = { age : 1};
let b = Object.assign({},a);
a.age = 2;
console.log(b.age) //1
~~~
特例:
~~~
a = { age : 1,test:{test:111}};
b = Object.assign({},a);
a.test.test = 222;
console.log(b.test.test) //1
~~~
* `...`运算符实现浅拷贝
~~~
let a = { age :1 }
let b = { ..a };
a.age = 2;
console.log(b.age)//1
~~~
特例:
~~~
a = { age :1 ,test:{test:111} }
b = { ...a };
a.test.test = 222;
console.log(b.test.test)//222
~~~
通常浅拷贝就能解决大部分问题,但是有时可能需要使用深拷贝
~~~
let a = { age: 1, jobs: { first: 'FE' } } let b = { ...a } a.jobs.first = 'native' console.log(b.jobs.first) // native
~~~
* 浅拷贝只解决了第一层的问题,如果接下去的值中还有对象的话,两者享有相同地址。要解决这个问题,就得使用深拷贝。
2. 深拷贝
* `JSON.parse(JSON.stringify(object))` 通常通过这个来解决
~~~
let a = { age: 1, jobs: { first: 'FE' } }
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native' console.log(b.jobs.first) // FE
~~~
问题局限性:
1. 会忽略undefined
2. 会忽略symbol
3. 不会序列化函数
4. 不能解决循环引用的对象
5. 没有null
~~~
let obj = { a: 1, b:{ c:2, d:3}}
obj.c = obj.b;
obj.e = obj.a;
obj.b.c = obj.b;
obj.b.e = obj.b.c;
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj)
~~~
会报错
![](https://box.kancloud.cn/409972a6fd757e514a8a08bc4a4d0b25_578x98.png)
* 在遇到函数、`undefined`或者`symbol`的时候,该对象也不能正常的序列化(没有null)
~~~
let a = {
age: undefined,
sex: Symbol('male'),
jobs: function() {},
name: 'yck'
}
let b = JSON.parse(JSON.stringify(a))
console.log(b) // {name: "yck"}
~~~
复杂数据都是可以序列化的,这个函数可以解决大部分问题
如果你所需要拷贝的对象含有内置类型并且不包含函数,可以使用MessageChanel
~~~
function structuralClone(obj) {
return new Promise(resolve => {
const { port1, port2 } = new MessageChannel()
port2.onmessage = ev => resolve(ev.data)
port1.postMessage(obj)
})
}
var obj = {
a: 1,
b: {
c: 2
}
}
obj.b.d = obj.b
// 注意该方法是异步的
// 可以处理 undefined 和循环引用对象
const test = async () => {
const clone = await structuralClone(obj)
console.log(clone)
}
test()
~~~
实现一个深拷贝,但实现一个深拷贝是很困难的,需要考虑好多种边界情况,比如原型链如何处理,DOM如何处理等,我们实现的深拷贝只是简易版,并且推荐使用**lodash的深拷贝函数**
~~~
function deepClone(obj){
function isObject(o){
return (typeof 0 ==='object' || typeof o ==='function') && o != null)
}
if(!isObjet(object)){
throw new Error('对象')
}
let isArray = Array.isArray(obj);
let newObj = isArray ? [...obj] : { ...obj};
Reflect.ownKeys(newObj).forEach( key =>{
newObj[key] = isObject(obj[key])?deepClone(obj[key]) :obj[key]
})
return newObj;
}
let obj = {
a:[1,2,3],
b:{c:2,d:3},
}
let new Obj = deepClone(obj);
newObj.b.c = 1;
console.log(obj.b.c) //2
~~~
`Object.keys()`返回属性key,但不包括不可枚举的属性
`Reflect.ownKeys()`返回所有属性key
### 原型
**如何理解原型?如何理解原型链?**
每个js对象都有`__proto__`属性,这个属性指向了原型。不推荐直接使用
![](https://box.kancloud.cn/e57fbd35931c93b7e6e9e142d7217b06_408x283.png)
可以发现一个`constructor属性`,就是构造函数
![](https://box.kancloud.cn/7642682bd6a668057f453fdb9c63cabb_578x411.png)
打开constructor属性,可以发现一个`prototype`属性,这个属性和`__proto_`_对应。
结论:**原型的constructor属性指向构造函数,构造函数又通过prototype属性指向原型,但是并不是所有函数都具有这个属性,Function.prototype.bind()就没有这个属性。**
![](https://box.kancloud.cn/3962ef83481b4cd5b4ce71058ab4a67b_478x599.png)
**原型链就是多个对象通过`__proto__`的方式连接起来的。**(我们创建一个对象,发现能使用很多没有定义过的函数)(为什么obj可以访问到valueOf函数,就是因为obj通过原型链找到了valueOf函数)
总结:
1. ` Object`是所有对象的爸爸,所有对象可以通过`__proto__`找到它
2. Function是所有函数的爸爸,所有函数可以通过`__proto__`找到它
3. 函数的prototype是一个对象
4. 对象的`__proto__`指向原型,`__proto__`将对象和原型两居起来组成了原型链
- 空白目录
- 双樾
- JS基础知识
- JS-WEB-API
- 开发环境
- 运行环境
- ES6
- 原型
- 异步
- 虚拟dom
- mvvm
- 组件化和React
- hybrid
- 其他
- 补充
- 技巧
- 快乐动起来呀
- css
- 掘金小册子
- js基础知识
- ES6知识点
- JS异步
- JS进阶知识
- 思考题
- DevTools Tips
- 浏览器基础知识
- 浏览器缓存机制0
- 浏览器渲染原理
- 安全防范知识点0
- 从V8中看JS性能优化0
- 性能优化琐碎事
- Webpack性能优化0
- 实现小型打包工具0
- React和Vue
- Vue生命周期
- vue基础知识点
- Vue响应式
- vue高级
- React基础
- Vue.js技术解密
- 准备工作
- 数据驱动
- new Vue()
- vue实例挂载
- 组件化
- 深入响应式原理
- 编译
- 扩展
- Vue Router
- Vuex