JS基础语法
变量类型计算
题目
typeof
能判断哪些类型- 何时使用
===
何时使用==
- 值类型和引用类型的区别
- 堆栈图
- 手写深拷贝
- 注意判断值类型和引用类型
- 注意判断是数组还是对象
- 递归
知识点
变量类型
-
值类型 vs 引用类型
-
typeof
运算符// 判断所有值类型 let a typeof a // 'undefined' const str = 'abc' typeof str // 'string' const n = 100 typeof n // 'number' const b = true typeof b // 'boolean' const s = Symbol('s') typeof s // 'symbol' // 判断函数 typeof console.log // 'function' typeof function () {} // 'function' // 识别引用类型(不能再继续识别) typeof null // 'object' typeof ['a', 'b'] // 'object' typeof { x: 100 } // 'object'
-
深拷贝
/** * 深拷贝 */ const obj1 = { age: 20, name: 'xxx', address: { city: 'beijing' }, arr: ['a', 'b', 'c', 'd'] } const obj2 = deepClone(obj1) obj2.address.city = 'shanghai' console.log(obj1.address.city) /** * obj 要拷贝的对象 */ 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(obj)){ // 递归调用 result[key] = deepClone(obj[key]) } } return result }
变量计算
-
类型转换
// 字符串拼接 const a = 100 + 10 // 110 const b = 100 + '10' // '10010' const c = true + '10' // 'true10'
原型和原型链
题目
- 如何准确判断一个变量是不是数组?
- a instanceof Array
- 手写一个简易的jQuery,考虑插件和扩展性
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) }) } } // const $p = new jQuery('p') // $p.get(1) // $p.each((elem) => console.log(elem.nodeName)) // $p.on('click', () => alert('clicked')) // 插件 jQuery.prototype.dialog = function (info) { alert(info) } // $p.doalog('abc') // 复写 (造轮子) class myJQuery extends jQuery { constructor(selector) { super(selector) } // 扩展自己的方法 addClass(className) { // ... } style(data) { // ... } }
- class的原型本质,怎么理解?
- 原型和原型链的图示
- 属性和方法的执行规则
知识点
class和继承
-
class
/** * - constructor * - 属性 * - 方法 */ // 类 class Student { constructor(name, number){ this.name = name this.number = number } sayHi() { console.log( `姓名 ${this.name} , 学号 ${this.number}` ) } } // 通过类 new 对象/实例 const xialuo = new Student('夏洛', 10000) console.log(xialuo.name) console.log(xialuo.number) xialuo.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}` ) } } // 通过类 new 对象/实例 const xialuo = new Student('夏洛', 10000) 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()
类型判断instanceof
xialuo instanceof Student // true
xialuo instanceof People // true
xialuo instanceof Object // true
[] instanceof Array // true
[] instanceof Object // true
{} instanceof Object // true
原型和原型链
原型
// class 实际上时函数,可见是语法糖
typeof Student // 'function'
typeof People // 'function'
// 隐式原型和显示原型
console.log(xialuo.__proto__)
console.log(Student.prototype)
console.log(xialuo.__proto__ === Student.prototype) // true
- 原型关系
- 每个
class
都有显示原型prototype
- 每个实例都有隐式原型
__proto__
- 实例的
__proto__
指向对应class
的prototype
- 每个
- 基于原型的执行规则
- 获取属性 或执行方法 时
- 先在自身属性和方法寻找
- 如果找不到则自动去
__proto__
中查找
原型链
console.log(Student.prototype.__proto__)
console.log(People.prototype)
console.log(People.prototype === Student.prototype.__proto__) // true
作用域和闭包
题目
- this的不同应用场景,如何取值?
- 注意:情况比较多,多看笔记
- 手写bind函数
function fn1(a, b) { console.log('this', this) console.log(a, b) return 'this is fn1' } const fn2 = fn1.bind({x: 100}, 10, 20) const res = fn2() console.log(res) // 模拟 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) } }
-
实际开发中闭包的应用场景,举例说明
- 隐藏数据
- 如做简单的cache工具
-
代码题
// 创建 10 个 `<a>`标签,点击的时候弹出来对应的序号 let i, a for (i = 0; i < 10; i++) { a = document.createElement('a') a.innerHTML = i = '<br>' a.addEventListener('click', function (e) { e.preventDefault() alert(i) }) document.body.appendChild(a) } // 解答:块级作用域 let a for (let i = 0; i < 10; i++) { a = document.createElement('a') a.innerHTML = i = '<br>' a.addEventListener('click', function (e) { e.preventDefault() alert(i) }) document.body.appendChild(a) }
知识点
作用域
- 全局作用域
window
对象document
对象
- 函数作用域
- 块级作用域(ES6新增)
自由变量
- 一个变量在当前作用域没有定义,但被使用了
- 向上级作用域,一层一层依次寻找,直到找到为止
- 如果到全局作用域都没找到,则报错 xx is not defined
闭包
- 作用域应用的特殊情况,有两种表现:
- 函数作为参数被传递
- 函数作为返回值被返回
this
- 作为普通函数(window)
- 使用 call apply bind(传入什么绑定什么)
- 作为对象方法被调用(返回对象本身)
- 在class方法中调用(返回当前函数本身)
- 箭头函数(找上级作用域的this值)
// this取什么值是在函数执行的时候确认的,不是在函数定义时候确认的 function fn1() { console.log(this) } fn1() // window fn1.call({ x: 100 }) // { x: 100 } const fn2 = fn1.bind({ x: 200 }) fn2() // { x: 200 } const zhangsan = { name: '张三', sayHi() { // this 即当前对象 console.log(this) }, wait() { // 对象方法 setTimeout(function() { // this === window console.log(this) }) // 箭头函数 取上级作用域的值 setTimeout(() => { // this 即当前对象 console.log(this) }) } }
异步
题目
- 同步和异步的区别是什么?
- 基于JS是单线程语言
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
- 手写用Promise加载一张图片
function loadImg(src) { return new Promise((resolve, reject) => { const img = document.createElement('img') img.onload = () => { resolve(img) } img.onerror = () => { const err = new Error(`图片加载失败 ${src}`) reject(err) // reject(new Error(`图片加载失败 ${src}`)) } img.src = src }) } // 使用 const url = 'https://111.png' loadImg(url).then(img => { console.log(img.width) return img }).then(img => { console.log(img.height) }).catch(err => console.error(err)) // 多个图片使用 const url1 = 'https://111.png' const url2 = 'https://222.png' 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(err => console.error(err))
- 前端使用异步的场景有哪些?
- 场景题
知识点
单线程和异步
- JS是单线程语言,只能同时做一件事儿
- 浏览器和 nodejs 已支持 JS 启动进程,如 Web Worker
- JS 和 DOM 渲染共用同一个线程,因为 JS 可修改 DOM 结构
- 遇到等待(网络请求,定时任务)不能卡住
- 需要异步
- 回调 callback 函数形式
应用场景
- 网络请求,如ajax图片加载
- 定时任务,如setTimeout
// ajax console.log('start') $.get('./data1.json', function (data1) { console.log(data1) }) console.log('end') // 图片加载 console.log('start') let img = document.createElement('img') img.onload = function () { console.log('loaded') } img.src = '/xxx.png' console.log('end') // setTimeout console.log(100) setTimeout(function () { console.log(200) }, 1000) console.log(300) // setInterval console.log(100) setInterval(function () { console.log(200) }, 1000) console.log(300)
callback hell 和 Promise
// callback hell 回调地域
// 获取第一份数据
$.get(url1, (data1) => {
console.log(data1)
// 获取第二份数据
$.get(url2, (data2) => {
console.log(data2)
// 获取第三份数据
$.get(url3, (data3) => {
console.log(data3)
// 还可能获取更多的数据
})
})
})
// Promise
function getData(url) {
return new Promise((resolve, reject) => {
$.ajax({
url,
success(data) {
resolve(data)
},
error(err) {
reject(err)
}
})
})
}
const url1 = '/data1.json'
const url2 = '/data2.json'
const url3 = '/data3.json'
getData(url1).then(data1 => {
console.log(data1)
return getData(url2)
}).then(data2 => {
console.log(data2)
return getData(url3)
}).then(data3 => {
console.log(data3)
}).catch(err => console.error(err))