JS 值类型和引用类型的区别
值类型
let a = 100 let b = a a = 200 console.log(b) // 100
出处:https://coding.imooc.com/lesson/400.html#mid=30282
引用类型
let a = {age: 20} let b = a b.age = 21 console.log(a.age) // 21
出处:https://coding.imooc.com/lesson/400.html#mid=30282
常见值类型(基本数据类型)
- 字符串(String)
- 数字(Number)
- 布尔(Boolean)
- 未定义(Undefined)
- 空(Null)
- Symbol
常见引用类型
- Object 类型
- Array 类型
- Function 类型
- Date 类型
- RegExp 类型
手写深拷贝
typeof 运算符
- 能判断所有的值类型
- 能判断函数
- 能识别引用类型(不能再继续识别)
示例 1:判断所有的值类型(基本数据类型)
let a = 'hello' let b = 11 let c = true let d let s = Symbol('hi') console.log(typeof a) // string console.log(typeof b) // number console.log(typeof c) // boolean console.log(typeof d) // undefined console.log(typeof s) // symbol
示例 2:能判断函数
let x = console.log let y = function() {} console.log(typeof x) // function console.log(typeof y) // function
示例 3:能识别引用类型(不能再继续识别)
console.log(typeof null) // object console.log(typeof ['hello']) // object console.log(typeof {age: 20}) // object
手撕深拷贝
普通写法:
const obj1 = { age: 20, name: '张三', address: { city: '北京' }, arr: ['x', 'y', 'z'] } const obj2 = deepClone(obj1) obj2.address.city = '上海' obj2.arr[0] = 'a' console.log(obj1.address.city) // 北京 console.log(obj1.arr[0]) // x // 深拷贝 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 }
简写形式:
// 深拷贝函数 function deepClone(obj) { // 1 判断是否是非引用类型或者null if (typeof obj !== 'object' || obj == null) return obj // 2 创建一个容器 let result = new obj.constructor() // 3 拿到对象的keys,给容器赋值 Object.keys(obj).forEach(v => result[key] = deepClone(obj[key])) // 4 返回容器 return result }
变量计算 - 类型转换
- 字符串拼接
- ==
- if 语句
- 逻辑运算
示例 1:字符串拼接
const a = 100 + 10 // 110 const b = 100 + '10' // '10010' const c = true + '10' // 'true10'
示例 2:==
100 == '100' // true 0 == '' // true 0 == false // true false == '' // true null = undefined // true
示例 2 扩展:
除了 == null 之外,其他都用 ===
const obj = {name: 'zhangsan'} if (obj.age == null) {} // 相当于: if (obj.age === null || obj.age === undefined) {}
示例 3:truly 变量和 falsely 变量
- truly 变量:!!a == true 的变量
- falsely 变量:!!a === false 的变量
以下是 falsely 变量,除此之外都是 truly 变量
!!0 === false !!NaN === false !!'' === false !!null === false !!undefined == false !!false === false
示例 4:逻辑判断
注:10 是 truly 变量,继续往后判断返回第二个值
console.log(10 && 0) // 0 console.log('' || 'abc') // 'abc' console.log(!window.abc) // true
原型和原型链
class 类
- constructor
- 属性
- 方法
示例:
// 学生类 class Student { constructor(name, number) { this.name = name this.number = number } sayHi() { console.log(`姓名 ${this.name}, 学号 ${this.number}`); } } // 通过类 new 对象/实例 const xialu = new Student('夏洛', '2022') console.log(xialu.name) // 夏洛 console.log(xialu.number) // 2022 xialu.sayHi() // 姓名 夏洛, 学号 2022
继承
- extends
- super
- 扩展或重写方法
示例:子类继承父类
// 父类 class People { constructor(name) { this.name = name } eat() { console.log(`${this.name} eat food`); } } // 子类 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('夏洛', '2022') console.log(xialuo.name) // 夏洛 console.log(xialuo.number) // 2022 xialuo.sayHi() // 姓名 夏洛, 学号 2022 xialuo.eat() // 夏洛 eat food // 实例 const wanglaoshi = new Teacher('王老师', '语文') console.log(wanglaoshi.name) // 王老师 console.log(wanglaoshi.major) // 语文 wanglaoshi.teach() // 王老师 教授 语文 wanglaoshi.eat() // 王老师 eat food
JS原型(隐式原型和显式原型)
类型判断 instanceof
console.log(xialuo instanceof Student) // true console.log(xialuo instanceof People) // true console.log(xialuo instanceof Object) // true
原型
class 实际上是 函数
console.log(typeof Student) // 'function' console.log(typeof Teacher) // 'function' console.log(typeof People) // 'function'
隐式原型和显式原型
- 隐式原型:__prop__
- 显式原型:prototype
实例对象的隐式原型等于对应构造函数的显示原型
console.log(xialuo.__proto__) // People {constructor: ƒ, sayHi: ƒ} console.log(Student.prototype) // People {constructor: ƒ, sayHi: ƒ} console.log(xialuo.__proto__ === Student.prototype) // true
出处:https://coding.imooc.com/lesson/400.html#mid=30288
原型关系
- 每个 class 都有一个显式原型 prototype
- 每个实例都有隐式原型 __proto__
- 实例的 __proto__ 指向对应 class 的 prototype
基于原型的执行规则
获取属性 xialuo.name 或执行方法 xialuo.sayHi 时先在自身属性和方法寻找如果找不到则自动去 __proto__ 中查找
原型链
instanceof 顺着隐式原型往上找,找到返回 true,找不到返回 false
出处:https://coding.imooc.com/lesson/400.html#mid=30289
hasOwnProperty
hasOwnProperty 会查找一个对象是否有某个属性,但是不会去查找它的原型链
console.log(xialuo.hasOwnProperty('name')) // true console.log(xialuo.hasOwnProperty('eat')) // false
手写简易 jQuery
通过 class类 和 原型,手写 jQuery 部分功能
<p>第一段文字</p> <p>第二段文字</p> <p>第三段文字</p> 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) { } }
测试:
作用域和闭包
作用域
- 全局作用域
- 函数作用域
- 块级作用域
全局作用域:如 window 对象、document 对象
函数作用域:只能在函数里面使用
块级作用域:在块内有效
示例:全局作用域
在任何地方都能获取到
window.a = 'zhangsan' function fn() { console.log(window.a) } fn() // zhangsan
示例:函数作用域
里面的函数能读取到外面函数的变量
function fn1() { let a = 'zhangsan' function fn2() { let b = 'lisi' console.log(a) } fn2() } fn1() // zhangsan
外面的函数不能读取里面函数的变量
function fn1() { let a = 'zhangsan' console.log(b) function fn2() { let b = 'lisi' } fn2() } fn1() // 报错:b is not defined
示例:块级作用域
在块之外读取不到变量
if (true) { let x = 100 } console.log(x) // 报错:x is not defined
示例:创建 10 个 <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) }
自由变量
- 一个变量在当前作用域没有定义,但被使用了
- 向上级作用域,一层一层依次寻找,直至找到为止
- 如果到全局作用域都没找到,则报错 xx is not defined
示例:不在当前作用域的就一层层往上找,a、b 都要往上层找
let a = 1 function fn1() { let b = 2 function fn2() { let c = 3 console.log(a + b + c) } fn2() } fn1() // 6
闭包
作用域应用的特殊情况,有两种表现:
- 函数作为返回值被返回
- 函数作为参数被传递
总结:所有自由变量的查找,是在函数定义的地方,向上级作用域查找,不是执行的地方
示例:函数作为返回值
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 :传入什么绑定什么(call、apply、bind的区别)
作为对象方法被调用:指向当前对象本身,异步指向 window,箭头函数的异步指向上级作用域
在 class 方法中调用:指向当前实例本身
箭头函数:取上级作用域 this 的值
注:this 的取值是在函数执行的时候确定的,不是在函数定义的时候确定的
示例 1:普通函数、使用 call、apply、bind
function fn1() { console.log(this) } fn1() // window
示例 2:call、apply、bind 指定指向
function fn1() { console.log(this) } fn1() // window fn1.call({x: 100}) // {x: 100} const fn2 = fn1.bind({x: 200}) fn2() // {x: 200}
示例 3:作为对象方法被调用、箭头函数
const zhangsan = { name: 'zhangsan', sayHi() { console.log(this) }, wait() { setTimeout(function() { console.log(this) }) }, waitAgain() { setTimeout(() => { console.log(this) }) } } zhangsan.sayHi() // this即当前对象 zhangsan.wait() // this === window zhangsan.waitAgain() // this即当前对象
示例 4:在 class 方法中调用
class People { constructor(name) { this.name = name } sayHi() { console.log(this) } } const zhangsan = new People('张三') zhangsan.sayHi() // this 指向张三对象
手写 bind
- 在函数原型上添加 bind1 方法,模拟 bind
- 将参数拆解为数组
- 通过 shift 方法挖走数组的第一项作为 this
- 把 this 赋值给 self,指向调用 bind 方法的函数
- 最后返回一个函数
// 模拟 bind Function.prototype.bind1 = function() { // 将参数拆解为数组 // const args = Array.prototype.slice.call(arguments) const args = Array.from(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)
实际开发中闭包的应用
- 隐藏数据
如做一个简单的 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')) // 100
异步
同步和异步的不同
单线程和异步
JS 是单线程语言,只能同时做一件事
浏览器和 nodejs 已支持 JS 启动进程,如 Web Worker
JS 和 DOM 渲染共用同一个线程,因为 JS 可修改 DOM 元素
遇到等待(网络请求,定时任务)不能卡住
需要异步
回调 callback 函数形式
异步和同步
- 基于JS是单线程语言
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
异步:callback 回调函数,等主线程任务执行完再执行
console.log(100) setTimeout(() => { console.log(200) }, 100) console.log(300) // 输出顺序:100 300 200
同步:按顺序执行,前面没执行完后面的不会执行
console.log(100) alert(200) console.log(300)
应用场景
- 网络请求,如 ajax、图片加载
- 定时任务,如 setTimeout
ajax:
console.log('start') $.get('xxx.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('start') setTimeout(function() { console.log('异步') }, 100) console.log('end')
异步相关面试题
手写 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 // src 一赋值,立马触发图片的加载 document.body.appendChild(img) }) return p } const url1 = 'xxx.png' const url2 = 'xx.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(error => console.err(error))
不积跬步无以至千里 不积小流无以成江海