undefined和null的区别
undefined 定义未赋值
null 定义并赋值,值为空
变量提升
用var声明变量存在变量提升的情况,用let和const不存以上情况
var a = 10;
function foo() {
console.log(a);
var a = 20;
console.log(a);
}
foo();
// 变量提升后的代码
var a = 10;
function foo() {
var a; //变量提升 提升函数作用域的最前面
console.log(a); //结果为undefined 因为var声明的变量提升后会在内存开辟空间,由于没有赋值 默认为undefined var声明的变量的初始化在赋值的位置
a = 20
console.log(a); //结果为20
}
foo();
//打印的结果
undefined
20
换成let或const的情况
let a = 10;
function foo() {
console.log(a); // 不能声明之前就进行访问
let a = 20; //let和const 让变量限制在他所在的作用域
}
foo();
//此种情况会报错
//ReferenceError: Cannot access 'a' before initialization
GET和POST的区别
1、get请求把传输的数据放在url中,所有人可见的,不安全。而post请求把传输的数据放在请求体中,不可见,比较安全
2、get请求对传输的数据的长度有限制,post请求没有限制
3、get请求的参数会被完整的保留在浏览器的历史记录中,post请求的参数不会保留
4、get请求的参数的数据类型只接受ASCLL字符,post请求的参数的数据类型没有限制
5、get请求只能进行url编码,post请求可接受多种编码方式
document.onload() 和 document.ready()两个事件的区别
document.ready() 指的是文档结构加载完成,不包括图片等非文字媒体文件
document.onload() 指的是页面包含的图片等所有的文件都被加载完成
js中数组的常用方法
1、reduce 累加器
var nums = [1,2,3,4,5,6];
var sum = nums.reduce((preValue,currentValue) => {
return preValue + currentValue;
},0);
console.log(sum);
//打印结果
21
2、some 遍历
var nums = [1,2,3,4,5,6]
var result = nums.some((item) => {
return item > 7 //数组里面的值都不大于7 所以result的值为false
})
//打印结果
false
var result1 = nums.some((item) => {
return item > 3 //存在大于3的item result1的值为true
})
//打印结果为
false
//总结 有任意一项的返回值为true或者都返回true 则最终结果为true 否则为false
3、every 遍历
var nums = [1,2,3,4,5,6]
var result = nums.every((item) => {
return item > 0 //所有返回值为true result的值才为true
})
console.log(result)
//打印结果为
true
var result1 = nums.every((item) => {
return item > 3//有任意一项的返回值为false为false result1的值为false
})
console.log(result1)
//打印结果
false
//总结 每一项返回值为true 最终结果为true 有任意一项的返回值为false 最终结果为false
4、filter 遍历
//遍历的结果会返回一个符合条件的新数组
var nums = [1,2,3,4,5,6]
var newArray = nums.filter((item) => {
return item > 3 //把返回值为true的item留在新数组中 最后返回这个新数组
})
console.log(newArray)
//打印结果
[4,5,6]
5、map 循环
var nums = [1,2,3,4,5,6]
var newArray = nums.map((item) => {
return item + 1
})
console.log(newArray)
//打印结果
[2,3,4,5,6,7]
6、forEach 循环
//遍历数组的每一项 无返回值 不支持IE
var nums = [1,2,3,4,5,6]
nums.forEach((item) => {
item += 1
console.log(item)
})
//打印结果
[2,3,4,5,6,7]
7 splice
//splice(index,参数1,参数2,参数3)
//1、参数1不为0
//(1)无后续的参数 从数组的index开始删除(参数1)个元素
//(2)有后续的参数 从数组的index开始删除(参数1)个元素,同时从数组的index处把后续的参数全添加进数组中
//2、参数1为0
//把后续的参数从数组的index开始添加进数组中
var nums = [1,2,3,4,5,6]
nums.splice(1,2)
console.log(nums)
//打印结果
[1,4,5,6]
nums.splice(1,0,7,8,9,10)
console.log(nums)
//打印结果
[
1, 7, 8, 9, 10,
2, 3, 4, 5, 6
]
8、sort()
var nums = [1,2,3,4,5,6]
nums.sort((a,b) => {
return b-a //从大到小排列 return a-b 从小到大排列
})
console.log(nums)
//打印结果
[6,5,4,3,2,1]
9、push() 返回数组元素的个数,从数组尾部添加元素
10、pop() 返回数组元素的个数,从数组尾部删除元素
11、unshift() 返回数组元素的个数,从数组头部添加元素
12、shift() 返回数组元素的个数,从数组头部添加删除元素
13、slice(start,end) 截取数组 start为负 从数组尾部开始截取 -1表示为最后一个元素,-2表示为倒数第二个
14、reverse() 翻转数组 改变原来的数组 不会创建新的数组
15、concat() 连接数组 不改变原先的数组
16、join(’-’) 将数组转化为字符串 以参数进行拼接
js中的数据类型有哪些?并说明是如何存储的?
js中有两个数据类型 分别是基本数据类型和引用数据类型
基本数据类型:string number boolean null undefined symbol(ES6) bigint(ES10)
引用数据类型: object
基本数据类型: 直接存储在栈内存中
引用数据类型: 每创建一个新的对象会在堆内存中创建一个新的空间,其内存地址在栈内存中存储
判断数据类型的方法
1、Object.prototype.toStrig.call(判断的对象)
2、typeof
3、instanceof
4、===
解释下js中的原型链
每一个函数都有一个prototype属性,当他作为构造函数时,
他实例化出来的函数对象会有一个__proto__属性(该属性和构造函数的prototype属性为同一个)。
当访问某个实例化对象的属性或者方法时,会在该实例化对象的自身查找,如果没有找到,
就回去他的__proto__隐式原型上去找,如果还没找到,
就会去构造函数的prototype属性中__proto__中查找。再没找到就返回undefined
这个链式查找的过程就就被称为原型链。
原型链继承的缺点?
function Parent(name) {
this.name = name
}
Parent.prototype.getName = function () {
return this.name
}
function Child() {
Parent.call(this,'jerry')
}
// Child.prototype = new Parent()
//Child.prototype = Parent.prototype
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
let child1 = new Child()
let child2 = new Child()
child2.name = 'tom'
console.log(child1.name);
console.log(child2.name);
1、子类原型对象指向同父类原型对象的实例,当子类有多个实例化对象时,修改其中一个会影响到其他的
2、在创建子类的时候,无法像继承元素传递参数
this指向
1、以函数形式调用时,this指向window
2、以方法调用时,this指向该方法的调用者
3、以构造函数调用时,this指向新创建的对象
4、使用call()和apply()调用是,this指向指定的对象(call()和apply()的第一个参数)
call()和apply()、bind()的区别
这三个方法都需要通过函数对象来进行调用
在调用这三个方法时,指定一个对象为这两个方法第一个参数
此时该函数对象的this就会指向这个指定的对象。
call()可以将实参在指定的对象之后依次传递给该函数对象: fun.call(obj,1,2)
apply()可以将实参在指定的对象之后以数组的方式传递给该函数对象: fun.apply(obj,[1,2])
bind() :
//使用方法
let bind = fun.bind(obj)
bind();
深拷贝和浅拷贝的区别
1、浅拷贝:
重新在堆中创建一块内存,拷贝前后对象中的基本数据类型互不影响,
拷贝前后对象中引用数据类型共享同一块内存,会相互影响。
实现方法:
Object.assign()
...扩展运算符
slice()方法
concat()方法
//浅拷贝
var obj = {
name: 'jack',
age: [1,2,3,3,4,5],
say: function () {
console.log('tom')
}
}
function qianCopy(obj) {
var copy = {}
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
copy[k] = obj[k]
}
}
return copy
}
var firstCopy = qianCopy(obj)
firstCopy.age[0] = 9
console.log(obj)
console.log(firstCopy)
2、深拷贝:建一个对象从内存中完整的拷贝一份出来,从堆内存中重新创建一个新的内存存放新对象,修改新对象不会影响原对象。
实现的方法:
递归
JSON.parse(JSON.stringify())
//深拷贝
var obj = {
name: 'jack',
age: [1,2,3,3,4,5]
}
function deepCopy(obj){
// 定义一个变量 并判断是数组还是对象
var cloneObj = Array.isArray(obj) ? [] : {};
if(obj && typeof obj === "object" && obj != null){
// 判断obj存在并且是对象类型的时候 因为null也是object类型,所以要单独做判断
for(var key in obj){ // 循环对象类型的obj
if(obj.hasOwnProperty(key)){ // 判断obj中是否存在key属性
if(obj[key] && typeof obj[key] === "object"){ // 判断如果obj[key]存在并且obj[key]是对象类型的时候应该深拷贝,即在堆内存中开辟新的内存
cloneObj[key] = deepCopy(obj[key]);
}else{ // 否则就是浅复制
cloneObj[key] = obj[key];
}
}
}
}
return cloneObj;
}
var cloneObj = deepCopy(obj)
obj.name = 'tom'
cloneObj.age[0] = 789
console.log(obj)
console.log(cloneObj)
封装js代码读取元素当前显示的css样式(考虑兼容性)
1、currentStyle()方法只适合IE浏览器,并不支持其他浏览器 (只能读取)
2、getComputedStyle() 适合IE9+以及其他正常的浏览器 (只能读取)
封装函数适合各种浏览器读取元素当前显示的css样式
function getStyle(obj,name) { // obj为读取样式的元素 name样式名
return window.getComputedStyle ? getComputedStyle(obj,null)[name] : obj.currentStyle[name]
}
3、元素.style.样式名 = 样式值 //读取或设置元素的内联样式 (可以设置)
怎样解决事件对象的兼容性?
事件对象:当事件的响应函数被触发时,浏览器每次都会将一个事件对象作为函数的实参
传递进响应函数,在事件对象中封装了当前事件相关的一切信息。比如鼠标的坐标、哪个键盘按键按下。
//在IE8浏览器中响应函数被触发时不会向该函数传递事件对象
//在IE8及以下的浏览器中事件对象作为window的属性存在的(window.event)
事件元素.事件 = function (event) {
event = event || window.event //解决事件兼容性的代码
}
两种事件绑定的区别?
1、元素.事件 = function () {] //此种方法只能给该元素同一个事件绑定一个响应函数,
//后面绑定会覆盖前面绑定的
2、元素.addEventListener(事件,callback ,false)
//参数
1、事件类型;(不要on)
2、响应事件的回调函数
3、是否在事件捕获阶段响应事件,默认false
//此种方法可以为该元素的同一个事件绑定多个回调函数,按顺序触发 this指向事件绑定的对象
//不支持IE8及以下的浏览器 支持IE8及以下浏览器可以使用attachEvent(事件,callback)
// 可以为该元素的同一个事件绑定多个回调函数 此事件要加on 后绑定先执行的顺序触发
// this指向window
3、addEventListener()和attachEvent() 进行封装进而适合所有的浏览器
function bind(obj,eventStr,callback) {
if (obj.addEventListener) {
obj.addEventListener(eventStr,callback,false)
} else {
obj.attachEvent("on" + eventStr, function () {
callback.call(obj) //将this指向绑定的事件
})
}
}
事件传播的流程
1、捕获阶段: 从最外层的祖先元素,向目标元素进行事件的捕获,默认此阶段不会触发事件
2、目标阶段:捕获到目标元素,捕获结束后在目标元素上触发事件
3、事件冒泡:从目标元素向祖先元素进行传递,依次触发祖先元素的事件
如果在捕获阶段想要触发事件,addEventListener()第三个参数设置为true,
一般情况下为false
阐述下事件委托
事件委托就是:把事件绑定给祖先元素,然后通过后代元素进行事件冒泡到该祖先元素进而触发事件。
BOM对象
1、window 网页中的全局对象
2、Navigator 通过该对象来识别不同的浏览器信息
Navigator对象中大部分属性都已不能用来识别浏览器,但是可以用navigator.userAgent
来识别,不同浏览器有不同的userAgent
3、History 操作浏览器进行向前向后翻页
4、Loaction 操作浏览器的url等相关的操作
判断数据类型的方法?
1、typeof 返回值为数据类型的字符串 不能区别null和object object和Array
2、instanceof 判断对象的具体类型
3、=== 判断 null和undefined
作用域与作用域链
1、理解:变量的使用范围就是作用域
2、分类:
全局作用域
函数作用域
块作用域(ES6才有)
3、作用:隔离变量,不同作用域下的同名变量不会有冲突
4、作用域链:多个嵌套的作用域形成的链,查找变量时是沿着作用域链来查找的。
查找规则:在当前作用域下查找要访问的变量,如果有直接返回,否则进入上一层
作用域查找,如果有直接返回,否者依次往上直至全局作用域为止,找到就返回,
找不到旧报错。
面试题
var obj = {
fn2: function () {
console.log(fn2)
}
}
obj.fn2()
上述执行的结果: 报错
var obj = {
fn2: function () {
//console.log(fn2)
console.log(this.fn2) //才可以打印对象中的方法
}
}
obj.fn2()
将伪数组转化为数组的方法
1、Array.prototype.slice.call(伪数组) //不支持IE8以下浏览器
2、[...伪数组]
2、考虑函数的兼容性封装函数
function fun(伪数组) {
var array = []
try {
array = Array.prototype.slice.call(伪数组)
} catch (e) {
for (var i = 0 ; i < 伪数组的长度 ; i++) {
array[array.length] = 伪数组[i]
}
}
return array
}
event-loop机制
event-loop :也就是js中的代码的执行顺序=>事件循环机制的运作原理,主要分为三大部分:
(1)、调用栈
(2)、微任务队列
(3)、消息队列
1、调用栈
event-loop开始的时候,会从全局一行一行的执行,遇到函数调用,会压到调用栈中,被压入的函数称之为帧,当函数返回后会从调用栈中弹出。
2、消息队列
js中的一些异步操作,比如fetch,setTimeout,setInterval,在被压入到栈中的时候里面的消息会进入到消息队列中去,消息队列中会等到调用栈清空之后再执行(也就是调用栈中的函数全执行完了后再执行)。
3、微任务队列
promise ,async,await等异步操作,在操作的时候会加入到微任务中去,也会在栈清空的时候立即执行,但是!!!=>调用栈中加入的微任务会立马执行,不需等待栈清空。
问题来了:如果同时存在微任务和消息队列,那么等栈结束的时候,微任务和消息队列事件谁先执行?
其机制是微任务队列会在消息队列前执行。微任务队列的优先级比消息队列高
闭包
1、如何产生闭包?的变量
当一个嵌套的内部函数引用了嵌套的外部函数的变量时,就会产生闭包。
2、常见的闭包
(1)、函数的返回值为函数
(2)、函数作为实参,传递给另一个函数调用
3、闭包的作用
(1)、使函数内部的局部变量在函数执行完后,仍然存活在内存中。
(2)、让函数外部可以操作函数内部的局部变量
4、缺点:
(1)、 函数执行完后,函数的局部变量没有释放,占用内存的时间变长
(2)、容易造成内存泄漏。
函数执行完后,函数内部声明的变量是否还存在?
一般情况下是不存在的,存在于闭包中的变量并才可能存在
(包含该引用变量的内部函数有引用指向时才存在)
内存溢出和泄漏
溢出:当程序运行需要的内存超过了剩余的内存时,会抛出错误
泄漏:占用的内存没有及时释放(内存泄漏过多导致内存溢出)
(1)、意外的全局变量
(2)、没有及时清理定时器或回调函数
(3)、闭包
(4)、脱离dom的引用
new 一个对象的背后有什么操作
1、创建一个空对象
2、给对象设值__proto__属性,并指向构造函数的prototype的属性值
3、执行函数构造题(给对象添加属性或方法)
rest参数和arguments参数的区别
1、ES5中用arguments来获取函数的实参 他的返回值是一个对象(类数组)
把类数组转化为对象
Array.prototype.slice.call(arguments)
2、ES6中用rest参数来代替arguments参数 返回是以一个数组 可以使用数组各种API
使用方法: rest参数要放在所有的参数之后
function fun(...args) {
console.log(args)
}
fun(1,2,3,4)
防抖和节流函数
1、防抖函数
function debounce(delay,callback) {
let timer = null
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
callback && callback(args)
},delay)
}
}
2、节流函数
function throttle(delay,callback) {
let timer = null
return functon(...args) {
if (!timer) {
timer = setTimeout(() => {
callback && callback(args)
timer = null
})
}
}
}
ES6新特性之声明变量
ES6中用let和const声明变量(具有块级作用域)
let:
(1)、使用let声明的变量具有块级作用域,只能在声明的代码块中使用,必须先声明再使用
(2)、具有暂时性死区特性: 代码如下
var num = 1;
if (true) {
console.log(num); //报错 'num未初始化'
let num = 2;
}
暂时性死区: if的这个块级作用域外用var声明num变量,内部用let声明了同名的变量,
在这时console.log()不会上一级作用域查找num变量,因为内部用let声明该变量,
使用let声明的num变量就与if内的代码块进行了绑定。所以输出'num 未初始化'
const:
(1)、 使用const声明的常量具有块级作用域,只能在声明的代码块中使用,必须先声明再使用
(2)、 声明常量时必须赋值,否则报错。
(3)、 使用const声明的常量一但赋值,其值为基本数据类型不可更改,如果为引用类型,不能
重新赋值,但是可以更改结构内部的值。
ES6新特性之变量的解构赋值和扩展运算符
1、数组
const F4 = ['a','b','c','d']
let [a,b,c,d] = F4
console.log(a,b,c,d) //打印 a,b,b,d
2、对象
const Person = {
name:'jack',
age: 45,
setName: function () {
console.log(this.name)
}
}
let {name,age,setName} = Person
console.log(name)
console.log(age)
setName()
3、扩展运算符
let people = ['jack','tom']
let dog = ['hehe','pipi']
let concatArray = [...people,...dog]
console.log(concatArray )
ES6新特性之模板字符串
1、声明字符串的新方法 反引号 ``
2、内容中可以直接出现换行符,其他方法不行
3、变量拼接必须要用${}
let name = 'tom'
let age = 18
let str = `${name}今天${age}岁了`
console.log(str) //打印 tom今天18岁了
ES6新特性之箭头函数
1、声明: let fun = (arguments) => {}
2、this的指向是静态的,this始终指向函数声明时所在作用域下的this的值
function getName() {
console.log(this.name)
}
let getName2 = () => {
console.log(this.name)
}
window.name = 'jack'
const school = {
name: 'tom'
}
getName() //jack
getName2() // jack
getName.call(school) // tom
getName2.call(school) // jack
3、不能作为构造函数实例化对象
4、没有arguments对象
ES6新特性之Promise
promise是ES6引入的异步编程的解决方案
1、需要执行异步操作的时候,new一个Promise对象
2、给新创建的Promise对象,设定一个在成功条件下回调和一个失败条件下的回调
3、Promise对象可以通过调用then()方法获得成功的回调信息
4、Promise对象可以通过调用catch()方法获得失败的回调信息
const p = new Promise((resolve,reject) => {
if (成功) resovle() // 成功条件下 返回成功回调的信息
else {
reject() //失败条件下返回 失败的回调信息
}
})
p.then((value) => {]
//获取成功回调的信息
)
p.catch((error) => {
//获取成功回调的信息
})
** then()方法返回的结果仍是一个Promise对象,对象的状态有then方法中的回调函数的执行结果
**决定
(1)、回调函数返回值是非Promise对象,then方法返回对象的状态为成功,
该返回值为then方法返回对象成功状态下的值
(2)、回调函数返回值是Promise对象,then方法返回对象的状态有该promise对象的状态来决定,
(3)、回调函数抛出错误,then方法返回对象的状态为失败,抛出错误的值为then方法返回对象失败状态的值
then方法的返回结果决定可以链式调用 如下
const fs = require('fs')
const p = new Promise((resolve, reject) => {
fs.readFile('./11.md', (err, data) => {
if (err) reject('读取失败')
resolve(data)
})
})
p.then((value) => {
console.log(value.toString())
return new Promise((resolve, reject) => {
fs.readFile('./22.md', (err, data) => {
if (err) reject('读取失败')
resolve(data)
})
})
}).then((value) => {
console.log(value.toString())
return new Promise((resolve, reject) => {
fs.readFile('./33.md', (err, data) => {
if (err) reject('读取失败')
resolve(data)
})
})
}).then((value) => {
console.log(value.toString())
}).catch((reason) => {
console.log(reason)
})
5、中断promise的链式调用,在其中返回一个pending状态的promise对象
ES6新特性之新的数据结构
1、set 集合
let s = new Set()
s.size() // 集合中元素个数
s.add(新元素) //添加元素
s.delete(元素) //删除元素
s.has(元素) //判断元素是否存在集合中 返回值为Boolean
s.clear() //清空集合
2、map
let m = Map()
//添加元素
1、m.set(属性名,属性值)
//删除元素
2、m.delete(属性名)
//获取元素
3、m.get(属性名)
//元素的个数
m.size()
//清空
4、m.clear()
ES6新特性之类
class Phone {
static name = '手机' //static 标注的属性 属于类不属于实例 实例无法访问
static change() {
console.log('我能改变世界')
}
constructor(brand,price) { // 构造函数 new 一个实例时 会自动执行
this.brand = brand
this.price = price
}
//方法必须使用改语法,
call(){
console.log('打电话')
}
}
class SmartPhone extends Phone {
constructor(brand,price,color,size) {
super(brand,price) //调用父类的构造函数进行初始化
this.color = color
this.size = size
}
photo() {
console.log('我能照相')
}
call() { //子类的方法于父类同名时 不会调用父类的方法 会重写该方法
console.log('我可以进行视频通话')
}
}
//let onePlus = new Phone('onePlus',4656)
let xiaomi = new SmartPhone('xiaomi',7895,'黑色',6.5)
xiaomi.photo()
xiaomi.call()
ES6新特性之模块化代码
优点:
(1)、防止命名冲突
(2)、代码复用
(3)、高维护性
export命令用于规定模块的对外接口
import命令用于输入其他模块的功能
ES8新特性之async和await
async函数
1、async函数的返回值为Promise对像,其状态和值由函数的的返回值来决定
async function fun() {
1、return 非Promise对象
2、return Promise对象
3、throw err 抛出错误
}
let result = fun() //result 为Promise对象
(1)、函数返回值是非Promise对象,async函数返回的Promise对像(result)的状态为成功,
该返回值为对象成功状态下的值
(2)、函数返回值是Promise对象,async函数返回的Promise对像(result)的状态和值由函数返回的promise对象的状态和值来决定,
(3)、函数抛出错误,async函数返回的Promise对像(result)的状态为失败,抛出错误的值为async函数返回的Promise对像(result)失败状态的值
await 表达式
(1)、必须写在async函数中
(2)、await 右侧的表达式一般为Promise对象
(3)、await 返回值为Promise对象成功的值
(4)、await的Promise对象失败了,抛出异常 通过try..catch来捕获
try {
let result = await Promise对象
} catch(e) {
console.log(e)
}
例子:
const p = new Promise((resolve,reject) => {
// resolve('成功了')
reject('失败了')
})
async function fun() {
try {
let result = await p
console.log(result)
} catch (e) {
console.log(e)
}
}
fun()