变量类型和计算
- typeof能判断哪些类型
- 何时使用=== 何时用 ==
- 值类型和引用类型的区别
// 值类型
let a = 100
let b = a;
a = 200;
console.log(a); // 200
console.log(b); // 100
// 引用类型
let a = { age: 20};
let b = a;
b.age = 21;
console.log(a.age); // 21
值类型互相并不影响
let a;不能用const会报错
typeof运算符
- 识别所有类型值
- 识别函数
- 判断是否是引用类型(不可再细分)
深拷贝和浅拷贝的区别
如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改B时,看A是否会发生变化,如果A也跟着变了,说明这是浅拷贝,拿人手短,如果A没变,那就是深拷贝,自食其力。
//浅拷贝
var obj = { a: 1, arr: [2, 3] };
var shallowObj = Object.assign({}, obj);
shallowObj.arr[1] = 0;
shallowObj.a = 'yanbo';
console.log(obj, 'obj');
console.log(shallowObj, 'shallowObj');
//深拷贝
<script>
// 深拷贝
const obj1 = {
name: "deepclone",
age: 18,
adress: {
city: "bejing"
},
arr: [1,2,3]
}
const obj2 = deepClone(obj1);
obj2.adress.city = "shanghai"
console.log(obj1.adress.city) //beijing
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// obj 是 null ,或者不是对象和数组,直接返回
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用!!!
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
</script>
字符串拼接
重点
原型和原型链
题目
1. 如何准确判断一个变量是不是数组?
2. class的原型本质,怎么理解?
3. 手写一个简易的jquery,考虑插件和扩展性
答:class的原型本质,怎么理解
1.原型和原型链的演示
2.属性和方法的执行规则(即 怎么通过隐式原型一步步通过链的方式向上找方法)
1.类和继承
// 类
class Student {
constructor(name, number) {
this.name = name
this.number = number
// this.gender = 'male'
}
sayHi() {
console.log(
`姓名 ${this.name} ,学号 ${this.number}`
)
// console.log(
// '姓名 ' + this.name + ' ,学号 ' + this.number
// )
}
// study() {
// }
}
// 通过类 new 对象/实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()
const madongmei = new Student('马冬梅', 101)
console.log(madongmei.name)
console.log(madongmei.number)
madongmei.sayHi()
继承通过:
extends
super:子执行父类的函数
扩展和重写
继承
// 父类
class People {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name} eat something`)
}
}
// 子类
class Student extends People {
constructor(name, number) {
super(name)
this.number = number
}
sayHi() {
console.log(`姓名 ${this.name} 学号 ${this.number}`)
}
}
// 子类
class Teacher extends People {
constructor(name, major) {
super(name)
this.major = major
}
teach() {
console.log(`${this.name} 教授 ${this.major}`)
}
}
// 实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()
xialuo.eat()
// 实例
const wanglaoshi = new Teacher('王老师', '语文')
console.log(wanglaoshi.name)
console.log(wanglaoshi.major)
wanglaoshi.teach()
wanglaoshi.eat()
2. instanceof可以判断引用类型
3.原型和原型链
5. prototype
在JavaScript中,prototype
是一个非常重要的概念,它主要用于实现继承和共享属性。每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型(prototype),每一个对象都会从原型"继承"属性。prototype
是函数的属性,所有函数都会有这个属性,这个属性指向了这个函数(构造函数)的原型对象。因此,所有的通过同一个构造器函数创建出来的实例都会共享这个原型对象的属性。如果一个实例需要访问一个属性,它首先查找自己是否有这个属性,如果没有,它会在原型链上查找,直到找到这个属性或者查找到null
为止。 疑问:通过字面量的方式创建的空对象原型是什么?
1,所有通过字面量方式创建的空对象都是以Object.prototype为原型的,比如当我们创建一个新的函数时。
例如下面代码
function Person() {
console.log(Person.prototype.__proto__ === Object.prototype)
}// 输出true
所以大多数情况下JavaScript 对象的
__proto__
属性指向函数的prototype属性,同时这个函数的prototype.constructor
属性又指回这个函数本身,形成一个环形引用。
6. __ proto __
在JavaScript中,一个对象的__proto__
属性指向的就是这个对象的构造函数的prototype
属性,即这个对象的原型对象。当我们通过new操作符创建新对象时,新对象的__proto__
属性会被赋值为构造器函数的prototype。但__proto__
属性并不是ECMAScript标准的一部分,而是一些浏览器特定的实现。在实际的编程中,通常不推荐直接使用__proto__
,而是应该使用Object.getPrototypeOf()方法来获取一个对象的原型。
1.什么是原型链
原型链(Prototype Chain)是 JavaScript 中实现继承的一种机制。它是基于对象的,每个 JavaScript 对象都有一个原型(Prototype)属性,它指向另一个对象。当我们访问对象的属性或方法时,如果该对象本身没有该属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或方法或达到原型链的顶端(即 Object.prototype)。
原型链
手写简易的jq
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector)
const length = result.length
for (let i = 0; i < length; i++) {
this[i] = result[i]
}
this.length = length
this.selector = selector
}
get(index) {
return this[index]
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i]
fn(elem)
}
}
on(type, fn) {
return this.each(elem => {
elem.addEventListener(type, fn, false)
})
}
// 扩展很多 DOM API
}
// 插件
jQuery.prototype.dialog = function (info) {
alert(info)
}
// “造轮子”
class myJQuery extends jQuery {
constructor(selector) {
super(selector)
}
// 扩展自己的方法
addClass(className) {
}
style(data) {
}
}
// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))
作用域和闭包
1.题目
1.this的不同应用场景,如何取值?
2.手写bind函数
3. 实际开发中闭包的应用场景,举例说明
2.知识点
1.作用域和自由变量
2.闭包
3.this
作用域:全局作用域、函数作用域、块级作用域(ES6新增,即if、while、for等{ }里面的)
自由变量:
- 一个变量在当前作用域没有定义,但是被使用了
- 向上级作用域,一层一层依次寻找,知道找到为止
- 如果到全局作用域都没有找到,则报错 xx is not defined
闭包:
- 作用域应用的特殊情况,有两种表现:
- 函数作为参数传递
- 函数作为返回值被返回
// 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找
// 不是在执行的地方!!!
// 函数作为返回值
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
应用场景:
- 当普通函数被调用,取window
- 使用call apply bind ,传的什么就绑定什么
- 作为对象方法被调用,返回对象本身
- 在class方法中调用,返回当前实例本身
- 箭头函数中,返回上级作用域的this值来确定
this取值是函数执行的时候确认的,不是在函数定义的时候确认的
箭头函数中this取上级作用域的值
左边,wait()里函数执行时settimeout触发的函数执行,如果是直接在wait()中打印this,指向的还是当前对象
手写bind函数
// 模拟 bind
Function.prototype.bind1 = function () {
// 将参数拆解为数组
const args = Array.prototype.slice.call(arguments)
// 获取 this(数组第一项)
const t = args.shift()
// fn1.bind(...) 中的 fn1
const self = this
// 返回一个函数
return function () {
return self.apply(t, args)
}
}
function fn1(a, b, c) {
console.log('this', this)
console.log(a, b, c)
return 'this is fn1'
}
const fn2 = fn1.bind1({x: 100}, 10, 20, 30)
const res = fn2()
console.log(res)
让i在循环中声明,作用域变为块级作用域,每次循环,生成一个块级作用域。
实际开发中闭包的应用
1.隐藏数据
2.做一个简单的cache工具(缓存)
// 闭包隐藏数据,只提供 API
function createCache() {
const data = {} // 闭包中的数据,被隐藏,不被外界访问
return {
set: function (key, val) {
data[key] = val
},
get: function (key) {
return data[key]
}
}
}
const c = createCache()
c.set('a', 100)
console.log( c.get('a') )
异步
异步和单线程
题目:
1. 异步和同步的区别是什么
2.手写promise加载一张图片
3.前端使用异步的场景有哪些
知识点:
- 单线程和异步
- 应用场景
- callback hell 和 Promise
1.异步和同步
- 异步基于js是单线程语言
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
3.异步应用场景
- 网络请求,如ajax图片加载
- 定时任务,如setTimeout
2.手写promise加载一张图片
Promise解决的是callback 的问题
Promise的构造函数接收一个参数,是函数,并且传入两个参数:resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。
Promise的优势在于,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。
Promise的作用,简单来讲,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。
大白话讲解Promise 大白话讲解Promise(一) - 吕大豹 - 博客园
function loadImg(src) {
const p = new Promise(
(resolve, reject) => {
const img = document.createElement('img')
img.onload = () => {
resolve(img)
}
img.onerror = () => {
const err = new Error(`图片加载失败 ${src}`)
reject(err)
}
img.src = src
}
)
return p
}
// const url = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
// loadImg(url).then(img => {
// console.log(img.width)
// return img
// }).then(img => {
// console.log(img.height)
// }).catch(ex => console.error(ex))
const url1 = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
const url2 = 'https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg'
loadImg(url1).then(img1 => {
console.log(img1.width)
return img1 // 普通对象
}).then(img1 => {
console.log(img1.height)
return loadImg(url2) // promise 实例
}).then(img2 => {
console.log(img2.width)
return img2
}).then(img2 => {
console.log(img2.height)
}).catch(ex => console.error(ex))
//请求某个图片资源
function requestImg(){
var p = new Promise(function(resolve, reject){
var img = new Image();
img.onload = function(){
resolve(img);
}
img.src = 'xxxxxx';
});
return p;
}
js异步进阶,一线大厂面试
一、event loop(事件循环/事件轮询)
- js是单线程
- 所以异步要基于回调来实现
- event loop就是js异步回调的实现原理
js如何执行?
- 从前到后,一行一行执行
- 如果某一行代码报错,则停止下面代码的执行
- 先把同步代码执行完,再执行异步
event loop过程
- 同步代码,一行一行放在Call Stack执行
- 遇到异步,会先记录下,等待时机(定时、网络请求等)
- 时机到了,就移动到Callback Queue中
- 如果Call Stack(调用栈)为空(即同步代码执行完),
- 执行当前的微任务
- 尝试DOM渲染,
- 触发Event loop,Event loop开始工作执行宏任务
- 轮询查找Callback Queue, 如果有则移动到Call Stack中执行
- 然后继续轮询查找
Dom事件和Event loop
$('#btn1').click()会立马执行,但是里面的回调函数function会放到web apis中等待,等事件点击触发后,回调函数function进入CallBack Queue中,Even Loop检测到,function就进入Call Stack中执行
二、Promise
三种状态:在进行中、已经解决、失败
状态回调
then和catch改变状态
// then() 一般正常返回 resolved 状态的 promise
Promise.resolve().then(() => {
return 100
})
// then() 里抛出错误,会返回 rejected 状态的 promise
Promise.resolve().then(() => {
throw new Error('err')
})
// catch() 不抛出错误,会返回 resolved 状态的 promise
Promise.reject().catch(() => {
console.error('catch some error')
})
// catch() 抛出错误,会返回 rejected 状态的 promise
Promise.reject().catch(() => {
console.error('catch some error')
throw new Error('err')
})
面试题
// 第一题
Promise.resolve().then(() => {
console.log(1) //1
}).catch(() => {
console.log(2) //不执行
}).then(() => {
console.log(3) //3
})
// 第二题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
console.log(1)
throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
console.log(2)
}).then(() => {
console.log(3)
})
//1 2 3
// 第三题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
console.log(1)
throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
console.log(2)
}).catch(() => {
console.log(3)
})
//1 2
三、async/await
async/await和promise的关系
- async 函数返回结果都是 Promise 对象(如果函数内没返回 Promise ,则自动封装一下)
```js
async function fn2() {
return new Promise(() => {})
}
console.log( fn2() )
async function fn1() {
return 100
}
console.log( fn1() ) // 相当于 Promise.resolve(100)
异步的本质
async function async1 () {
console.log('async1 start') //2
//async是定义函数,同步操作,遇到就先执行
await async2() //await后面都可以看作是callback里的内容,即异步
//类似event loop中setTimeout ()
//Promise.resolve().then(()=>{console.log('')})
console.log('async1 end') // 5 关键在这一步,它相当于放在 callback 中,最后执行
}
async function async2 () {
console.log('async2') //3
}
console.log('script start') //1
async1()
console.log('script end') //4
即,只要遇到了 `await` ,后面的代码都相当于放在 callback 里。
for...of
// 定时算乘法
function multi(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num * num)
}, 1000)
})
}
// // 使用 forEach ,是 1s 之后打印出所有结果,即 3 个值是一起被计算出来的
function test1 () {
const nums = [1, 2, 3];
nums.forEach(async x => {
const res = await multi(x);
console.log(res);
})
}
test1();
// 使用 for...of ,可以让计算挨个串行执行
async function test2 () {
const nums = [1, 2, 3];
for (let x of nums) {
// 在 for...of 循环体的内部,遇到 await 会挨个串行计算
const res = await multi(x)
console.log(res)
}
}
test2()
四、宏任务MacroTask和微任务MicroTask
宏任务与微任务
event loop与Dom渲染
微任务和宏任务的区别
// 修改 DOM
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container')
.append($p1)
.append($p2)
.append($p3)
// // 微任务:渲染之前执行(DOM 结构已更新)
Promise.resolve().then(() => {
const length = $('#container').children().length
alert(`micro task ${length}`)
})
// 宏任务:渲染之后执行(DOM 结构已更新)
setTimeout(() => {
const length = $('#container').children().length
alert(`macro task ${length}`)
})
- 微任务是ES6语法规定的
- 宏任务是浏览器规定的
所以,宏任务和微任务执行的队列不一样
asyn/await语法题
第一题:
返回是promise
返回是 100
- 因为async返回的是promise
- await后面接promise,相当于promise的then
第二题:
打印出start ‘a’,100 ‘b’,200
因为,await后面接100,相当于将100直接返回,搜易a为100
await后面接promise,相当于promise的then,所以resolve后面then可以执行,Promise.reject(300)后面是执行catch,所以到c处不执行,后面的都不执行
面试题
async function async1 () {
console.log('2---async1 start')
await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行
console.log('6---async1 end') // 上面有 await ,下面就变成了“异步”,类似 cakkback 的功能(微任务)
}
async function async2 () {
console.log('3---async2')
}
console.log('1---script start')
setTimeout(function () { // 异步,宏任务
console.log('8---setTimeout')
}, 0)
async1()
// 初始化promise时,传入的函数会被立即执行
new Promise (function (resolve) { // 返回 Promise 之后,即同步执行完成,then 是异步代码
console.log('4---promise1') // Promise 的函数体会立刻执行
resolve()
}).then (function () { // 异步,微任务
console.log('7---promise2')
})
console.log('5---script end')
// 同步代码执行完之后,屡一下现有的异步未执行的,按照顺序
// 1. async1 函数中 await 后面的内容 —— 微任务
// 2. setTimeout —— 宏任务
// 3. then —— 微任务
//初始化promise时,传入的函数会被立即执行,后面的then就是异步,为微任务