原始类型有哪几种?null 是对象嘛?
原始类型有6种
- string
- number
- boolean
- null
- undefined
- symbol
原始类型存储的都是值,没有函数调用
- 比如undefined.toString();会报错
- ‘1’.toString() 可以被调用,是因为字符串被强制转换成了String类型,也就是对象类型;
null 不是对象
- 虽然 typeof null = object ,但是不是对象
对象类型和原始类型的不同之处?函数参数是对象会发生什么问题?
原始类型存储的是值,对象类型存储的是指针,也就是引用类型;
对象类型复制,复制的是变量的指针。
看下面代码
- 最后a和b的值是一样的。也就是说引用类型复制,一个改变,另一个也改变。
let a = []; let b = a; b.push(1) console.log(a) // [1] console.log(b) // [1]
- 最后a和b的值是一样的。也就是说引用类型复制,一个改变,另一个也改变。
看下函数参数是对象
function test(person){ person.age = 1; person = { name: 'aaa', age:2 } return person; } let p1 = { name: 'bbb', age:3 } let p2 = test(p1) console.log(p1) // {name:'bbb',age:1} console.log(p2) // {name:'aaa',age:2}
- 可以看出来p1的age改变了。因为p1是引用类型,被当作函数参数传递,相当于复制,在函数中改变参数person,则p1也会跟着改变。所以p1.age = 1;
- p2是函数的返回值,也就是函数中的person
typeof 是否能正确判断类型?instanceof 能正确判断对象的原理是什么?
typeof不能正确判断类型,能判断基本类型,除了null
typeof '1' // string typeof 1 // number typeof false // boolean typeof undefined // undefined typeof Symbol() // sybmol typeof null // object
typeof对于对象,除了函数,都是object
typeof [] // object typeof {} // pbject typeof function fn(){} // function
值类型存处于栈内存
let a = 100; let b = a; b = 200; console.log(a) // 100 console.log(b) // 200
- 引用类型存处于堆内存
let obj1 = { a: 20 }; let obj2 = obj1; obj2 = { a : 211 } console.log(a) // { a: 21 } console.log(b) // { a: 21 }
instanceof能正确判断对象原理是通过原型链
const Person = function() {} const p1 = new Person() p1 instanceof Person // true var str = 'hello world' str instanceof String // false var str1 = new String('hello world') str1 instanceof String // true
### == 和 === 有什么区别?
#### 判断流程
+ 1.首先会判断两者类型是否相同。相同的话就是比大小了
+ 2.类型不相同的话,那么就会进行类型转换
+ 3.会先判断是否在对比 null 和 undefined,是的话就会返回 true
+ 4.判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number
+ 5.判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断
+ 6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断
### 什么是闭包?
+ 闭包:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。可以间接访问函数内部的变量。
+ 面试题:循环中使用闭包解决 `var` 定义函数的问题
- 下面会依次打出6个6
```js
for(var i = 1;i<=5;i++){
setTimeout(function timer(){
console.log(i)
},i*1000)
}
```
- 解决办法:
- 1.闭包
```js
for(var i = 1;i<=5;i++){
(function(j){
setTimeout(function timer(){
console.log(j)
},j*1000)
})(i)
}
```
- 2.使用setTimeout第三个参数
```js
for(let i = 1;i<=5;i++){
setTimeout(function timer(){
console.log(i)
},i*1000,i)
}
```
- 3.使用let
```js
for(let i = 1;i<=5;i++){
setTimeout(function timer(){
console.log(i)
},i*1000)
}
```
### 深浅拷贝
+ 先看下面一段代码
```js
let a = {
age: 1
}
let b = a;
b.age = 2;
console.log(a) // { age: 2}
- 看出把a复制给b,当b改变了,a也随着改变。因为a是引用类型;可以通过拷贝解决这个问题;
浅拷贝
- 通过Object.assign
let a = { age: 1 } let b = Object.assign({},a); b.age = 2; console.log(a) // { age: 1}
- 通过展开运算符
let a = { age: 1 } let b = { ... a }; b.age = 2; console.log(a) // { age: 1}
- 如果对象是多层,上面方法就不能解决了,需要用到深拷贝
let a = { age: 1, jobs: { first: 'FE' } } let b = { ...a } a.jobs.first = 'native' console.log(b.jobs.first) // native
深拷贝
- 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.不能解决循环引用的对象
- 但是有局限性。
var、let 及 const 区别?
- 函数提升优先于变量提升,函数提升会把整个函数挪到作用域顶部,变量提升只会把声明挪到作用域顶部
- var 存在提升,我们能在声明之前使用。let、const 因为暂时性死区的原因,不能在声明前使用
- var 在全局作用域下声明变量会导致变量挂载在 window 上,其他两者不会
- let 和 const 作用基本一致,但是后者声明的变量不能再次赋值
类型转换
- 字符串拼接
100 + 10 = 110 100 + '10' = '10010' true + '10' = 'true10'
- == 运算符
100 == '100' // true 0 == '' // true 0 == false // true false == '' // true null == undefined // true
数组方法会改变原数组
改变原数组
- pop
- push
- shift
- unshift
不改变原数组
- concat
- map
- forEach
- some
- every
- find
- filter
- slice
let arr = [1,2,3,4];
// let popRes = arr.pop()
// console.log(popRes,arr) // 4 [1,2,3]
// let pushRes = arr.push(5) // length
// console.log(pushRes,arr) // 5 [1,2,3,4,5]
// let shiftRes = arr.shift()
// console.log(shiftRes,arr) // 1 [2,3,4]
// let unshiftRes = arr.unshift(0)
// console.log(unshiftRes,arr) // 5 [0,1,2,3,4]
// 不改变原数组
// let concatRes = arr.concat([5,6,7])
// console.log(concatRes,arr) // [1,2,3,4,5,6,7] [1,2,3,4]
// let mapRes = arr.map(item => item+1)
// console.log(mapRes,arr) // [2,3,4,5] [1,2,3,4]
// let filterRes = arr.filter(item => item > 2)
// console.log(filterRes,arr) // [3,4] [1,2,3,4]
// let sliceRes = arr.slice(1)
// console.log(sliceRes,arr) // [2,3,4] [1,2,3,4]
// forEach 不返回数据, 不改变原数组
// arr.forEach(item => item+1)
// console.log(arr)
// 返回布尔值,不改变原数组
// let someRes = arr.some(item => item > 2)
// console.log(someRes,arr) // true [1,2,3,4]
// 返回布尔值,不改变原数组
// let everyRes = arr.every(item => item>2)
// console.log(everyRes,arr)
// 返回第一个为true的元素,不改变原数组
// let findRes = arr.find(item => item > 2)
// console.log(findRes,arr) // 3 [1,2,3,4]
从输入url到渲染出页面的整个过程:
Window.onload 和 DOMContentLoaded区别
Window.onload 和 DOMContentLoaded区别
异步
- 因为js是单线程,所以异步不会阻塞代码执行,同步会阻塞代码执行
// 异步 console.log(1) setTimeout(() => { console.log(2) }) console.log(3) // 结果: 1 3 2
// 同步
console.log(1)
alert(2)
console.log(3)
// 结果:1 2 3
+ 异步应用场景
- 网络请求
- 定时任务
### 作用域和闭包
#### 作用域
+ 全局作用域
+ 函数作用域
+ 块作用域
![image-20200606152240882](source/_posts/JS基础知识点及常考面试题/image-20200606152240882.png_posts/JS基础知识点及常考面试题/image-20200606152240882.png)
#### 自由变量
+ 1.一个变量在当前作用域没有定义,但被使用了
+ 2.向上级作用域去寻找,一层层找,直到找到为止
+ 3.如果全局作用域也没有,就会报错
> 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找
> 不是在执行的地方!!!
#### 闭包
```js
// 函数作为返回值
function create() {
const a = 100
return function () {
console.log(a)
}
}
const fn = create()
const a = 200
fn() // 100
// 函数作为参数被传递
function print(fn) {
const a = 200
fn()
}
const a = 100
function fn() {
console.log(a)
}
print(fn) // 100
this不同应用场景:
- 普通函数调用
- 使用call apply bind调用
- 作为对象方法被调用
- 在class方法中调用
- 箭头函数
this取值:
- 是在函数执行时确定的,不是函数定义的时候
function f1(){ console.log(this) }
// 普通函数
f1() // window
// call
f1.call({a:2}) // { a: 2 }
// bind
let f2 = f1.bind({b:1})
f2() // { b: 1}
const zhansan = {
name: ‘张三’,
sayHi(){
console.log(this)
},
wait(){
setTimeout(function(){
console.log(this)
},100)
}
}
zhansan.sayHi() // zhangsan
zhansan.wait() // window
const lisi = {
name: ‘李四’,
sayHi(){
console.log(this) // lisi
},
wait(){
// 箭头函数
setTimeout(() => {
console.log(this)
})
}
}
lisi.sayHi() // lisi
lisi.wait() // lisi
// class
class People{
constructor(name){
this.name = name;
}
sayHi(){
console.log(this)
}
}
let p = new People(‘aaa’)
p.sayHi() // p
### 如何识别浏览器类型
### 分析拆解url各个部分
### 原型
Class
```javascript
class Student{
constructor(name,age){
this.name = name;
this.age = age;
}
sayHi(){
console.log(`${this.name}:${this.age}`)
}
}
let xiaofei = new Student('小飞',10)
console.log(xiaofei.name)
console.log(xiaofei.age)
xiaofei.sayHi()
继承:
extends super constructor
class People{
constructor(name){
this.name = name;
}
sayHi(){
console.log(`${this.name}`)
}
}
class Student extends People{
constructor(name,number){
super(name)
this.number = number;
}
eat(){
console.log(`${this.number}`)
}
}
let s1 = new Student('小明',12)
console.log(s1.name)
console.log(s1.number)
s1.sayHi()
s1.eat()
instanceof类型判断
s1 instanceof Student // true
s1 instanceof People // true
s1 instanceof Object // true
[] instanceof Array // true
[] instanceof Object // true
{} instanceof Object // true
原型
class People{
constructor(name){
this.name = name;
}
sayHi(){
console.log(`${this.name}`)
}
}
class Student extends People{
constructor(name,number){
super(name)
this.number = number;
}
eat(){
console.log(`${this.number}`)
}
}
let s1 = new Student('小明',12)
console.log(s1.__proto__ === Student.prototype) // true
原型关系:
1.每个class都有显示原型prototype
2.每个实例都有隐士原型proto
3.实例的 proto===class的prototype
原型属性寻找规则:
1.先在自身上找属性和方法
2.自身没有,就去实例的隐士原型去找,也就是class的实例原型prototype
原型链
console.log(s1.__proto__ === Student.prototype) // true
console.log(People.prototype === Student.prototype.__proto__)// true
console.log(Object.prototype === People.prototype.__proto__)// true
console.log(s1.hasOwnPrototype('name')) // true 。 hasOwnProperty() 判断属性是否在这个实例上
console.log(s1.hasOwnPrototype('sayHi')) // false
attr和property区别:
1.property:修改对象属性,不会提现到html结构中
2.attribute:修改html属性,会改变html结构
3.两者都有可能引起dom重新渲染
4.尽量使用property
let p1 = document.getElementById('p1')
// property
p1.style.width = '200'
// attribute
p1.setAttribute('width',"200")
如何捕获js的异常
1.try catch 手动捕获
2.window.onerror
对跨域的js,如cdn 不会有详细的报错信息
对应压缩的js,需要配合sourcemap 反查到未压缩的代码行列
new Object()和Object.create()区别
1.{}等于new Object()。原型是Object.prototype
2.Object.create(null) 没有原型
3.Object.create({…}) 指定原型
let obj1 = {
a:1,
b:2,
sum:function(){
return this.a + this.b
}
}
let obj2 = new Object({
a:1,
b:2,
sum:function(){
return this.a + this.b
}
})
console.log(obj1 === obj2 ) // false
let obj3 = new Object(obj1)
console.log(obj3 === obj1 ) // true
let obj4 =Object.create(null)
console.log(obj4); // 什么也没有
let obj5 = new Object()
console.log(obj5) // 有原型
let obj6 = Object.create(obj1)
console.log(obj6) // 空对象。有原型
obj6.__proto === obj1; // true ***
console.log(obj6 === obj1) // false
数组slice 和 splice 的区别:
1.功能区别:slice切片 splice剪接
2.参数和返回值
3.是否是纯函数
let arr = [1,2,3,4]
// slice
let arr1 = arr.slice(startIndex,endIndex)
// splice
// 插入
let spliceRes = arr.splice(1,2,'a')
console.log(spliceRes,arr) // [2,3] [1,'a',4]
// 增加
let spliceRes = arr.splice(1,0,'a')
console.log(spliceRes,arr) // [] [1,'a',2,3,4]
//删除
let spliceRes = arr.splice(1,2)
console.log(spliceRes,arr) // [2,3] [1,4]
[10,20,30].map(parseInt)返回结果:
[10,20,30].map((num,index) => {
return parseInt(num,index)
})
// 相当于
parseInt(10,0) // 10
parseInt(20,1) // NaN
parseInt(30,2) // NaN