继承
javascript
// 类:抽象的,泛指的,一个大类 ===> 构造函数可以当做一个类
// 面向对象的三大特征:封装,继承,多态
// 继承:子类继承(拥有)父类的属性和方法
// 实现继承的方式~~~ 八股文
/* ==================== start ===================== */
// 1. 原型链继承
// 2. 借用构造函数(经典继承)
// 3. 组合继承
// 4. 原型式继承
// 5. 寄生式继承
// 6. 寄生式组合继承
/* ==================== end ===================== */
// ES6.
// 7. extends 继承 ===> 语法糖 --> 寄生式组合继承
function Parent(name){
this.name = name
}
function Child(name){
this.name = name
}
原型链继承
核心:让子类的原型等于父类构造函数的实例 Child.prototype = new Parent()
javascript
// 继承:子类拥有了父类的属性和方法
// 父类
function Parent(name){
this.name = name || '父类'
this.arr = [1, 2, 3]
}
// 子类
function Child(hobby){
this.hobby = hobby
}
// 原型链继承核心:让子类的原型 等于 父类构造函数的实例
Child.prototype = new Parent()
Child.prototype.constructor = Child
const boy1 = new Child('dance')
const boy2 = new Child('sing')
console.log(boy1, boy2)
// Q:想让boy1, boy2 这两个实例可以访问到 name / arr ?
// 原型链==>
// 每个对象通过__proto__都可以访问到它的原型,原型也有它的原型
// 当自己本身找不到这个属性的时候,就可以沿着__proto__往上找
// 如果,我们让子类的原型(Child.prototype)等于 父类构造函数的实例 (new Parent())
// 1. 首先Child构造函数创建的实例boy1,可以访问到Child.prototype上定义的属性(它自己的原型)
// 2. 现在 Child.prototype = new Parent() 即,如下
// Child.prototype = new Parent()
// Child.prototype = {
// name:'父类',
// arr: [1, 2, 3]
// }
// 3. 那么,boy1 ,理所应当,可以访问到name和arr了
原型链继承优缺点
javascript
// 原型链继承的优缺点
// 优点:
// 1. 由于本例方法定义在父类的原型上,子类的实例也共享父类原型上的方法
boy1.sayHi()
boy2.sayHi()
console.log(boy1.sayHi === boy2.sayHi)
// 缺点:
// 1. 子类在实例化的时候,不能给父类构造函数传参
const boy3 = new Child('爱好')
console.log(boy1.name)
console.log(boy2.name)
console.log(boy1.name === boy2.name)
// 2. 子类实例共享了父类的构造函数的属性和方法,
// 如果父类的属性值是一个引用类型(数组对象等), 子类实例修改这个值后会相互影响
console.log(boy1.arr)
boy1.arr.push(666)
console.log(boy2.arr)
// ===> 基本不用这个继承
// 注意:因为修改了子类的原型 ===> constructor它丢失了 我们可以修正一下
// boy1的原型的constructor 属性值,应该是Child, 但是现在打印了Parent
console.log(boy1.constructor) // Child.prototype.constructor
数据类型
javascript
// ES变量有两种不同类型的数据 原始值 和 引用值
// 也就是基本数据类型(简单)和 引用数据类型(复杂)
/* ==================== 面试题 ===================== */
// Q1: 数据类型有哪些?
// 基本数据类型:Number String Boolean / undefined null / Symbol BigInt
// 引用类型:Object ==> Function / Array / RegExp / Date / Math / Error等
// Q2: 基本数据类型和引用类型在内存中如何存储的?
// 栈(stack): 基本数据类型的值,存在栈里面
// 堆 (heap) : 引用数据类型,栈里面存的是地址,这个地址指向堆内存中的数据
/* ==================== end ===================== */
// 1. 基本数据类型不可以添加属性和方法
let str = '123'
str.a = 222
str.max = function(){
console.log(123)
}
console.log(str.a) // undefined
console.log(str.max) // undefined
// str.max()
// 2. 基本数据类型是按值访问的,也就是说,操作的是存储在栈里面的那个值
let a = 1
let b = 2
a = b
console.log(a, b)
a = 3
b = 4
// 3. 引用类型,直接赋值,实际上赋的是一个地址(指针)
let obj1 = {name:'wa~', age:6}
let obj2 = obj1
obj1.name = '早睡早起身体棒'
console.log(obj2)
typeof
javascript
// 1. typeof 可以用来判断一个变量是否为除了null的基本类型(原始类型)
// 基本类型
console.log(typeof 1);
console.log(typeof "");
console.log(typeof true);
console.log(typeof undefined);
console.log(typeof null); // object---有点儿特殊 Bug
console.log(typeof Symbol('id'))
console.log(typeof 9007199254740999n)
console.log(typeof BigInt(9007199254740999))
// 2. typeof 总是返回一个字符串
console.log(typeof (typeof 1)) // string
console.log('------------------')
// 3. typeof 检测对象(引用类型), 除了function, 其他都是'object'
// 注意:它不能区分数组
// 引用类型
console.log(typeof [1,2,3]); // 'object'
console.log(typeof function(){}); // 'function'
console.log(typeof {}); // 'object'
let date = new Date();
let error = new Error();
console.log(typeof date); // 'object'
console.log(typeof error); // 'object'
instanceof
javascript
// instanceof可以用于引用类型的检测, 但对基本数据类型无效.
// 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
// ==> 检测实例对象是不是构造函数创建的,或者是否属于某个祖先构造函数.
// 基本类型 无效
console.log('1' instanceof String) // false
console.log(1 instanceof Number) // false
console.log(true instanceof Boolean)
console.log( Symbol('id') instanceof Symbol)
console.log( 9007199254740999n instanceof BigInt)
console.log( BigInt(9007199254740999) instanceof BigInt)
console.log('--------------')
// 引用类型
console.log([] instanceof Array) // true
console.log(function () {} instanceof Function) // true
console.log({} instanceof Object) // true
完美
javascript
let num = 123
let str = '123'
let bool = true
let und = undefined
let nul = null
let symb = Symbol('id')
let big = 9007199254740999n
let arr = [1,2,3]
let obj = {}
let fn = function(){}
let date = new Date() // [object Date]
let reg = /a/g // [object RegExp]
let error = new Error() // [object Error]
// 前面的object小写, 后面的类型, 大写开头; 字符串
console.log(Object.prototype.toString.call(num)) // [object Number]
console.log(Object.prototype.toString.call(str)) // [object String]
console.log(Object.prototype.toString.call(bool)) // [object Boolean]
console.log(Object.prototype.toString.call(und)) // [object Undefined]
console.log(Object.prototype.toString.call(nul)) // [object Null]
console.log(Object.prototype.toString.call(symb)) // [object Symbol]
console.log(Object.prototype.toString.call(big)) // [object BigInt]
console.log(Object.prototype.toString.call(arr)) // [object Array]
console.log(Object.prototype.toString.call(obj)) // [object Object]
console.log(Object.prototype.toString.call(fn)) // [object Function]
console.log(Object.prototype.toString.call(date)) // [object Date]
console.log(Object.prototype.toString.call(reg)) // [object RegExp]
console.log(Object.prototype.toString.call(error)) // [object Error]
// Q:封装一个checkType的方法,可以检测数据类型?
const tempArr = [num, str, bool, und, nul, symb, big, arr, obj, fn, date, reg, error]
javascript
// Q:封装一个checkType的方法,可以检测数据类型?
const tempArr = [num, str, bool, und, nul, symb, big, arr, obj, fn, date, reg, error]
const checkType = (arr) => {
// arr.forEach(el => {
// console.log(Object.prototype.toString.call(el))
// });
const res = arr.map(el => {
return Object.prototype.toString.call(el)
})
return res
}
console.log(checkType(tempArr))
判断是否是数组
javascript
/* ==================== 判断数组的方式~ ===================== */
// 1. A instanceof Array
// 2. Object.prototype.toString.call()
// 3. Array.isArray()
// 4. arr.constructor === Array ==> 少,constructor可能会丢失
/* ==================== end ===================== */
const arr = [1, 2, 3]
// const res = Array.isArray(arr)
console.log(arr.constructor === Array)
深浅拷贝
赋值
javascript
// 赋值: 将某一值或者对象赋给某个变量的过程
// 基本数据类型:值传递,赋值之后两个变量互不影响
// 引用数据类型:赋址(地址),两个变量具有相同的引用,指向同一个对象,相互之间有影响
let a = '苟有恒,何必三更眠五更起'
let b = a
a = '早睡早起'
console.log(a)
console.log(b)
// 引用类型
let obj1 = {
name: "JS",
book: {
title: "You Don't Know JS",
price: "169"
}
}
let obj2 = obj1
obj2.name = 'Go'
console.log(obj1)
// 赋值 不是 浅拷贝,对象并没有新创建,还是那个对象,那个地址~~~
// 实际开发中,不希望改变obj2影响到obj1, 所以需要用到浅拷贝、深拷贝
浅拷贝
javascript
let obj1 = {
name: "JS",
book: {
title: "You Don't Know JS",
price: "169"
msg:{
demo:'1231231792831'
}
}
// 什么是浅拷贝? 面试题
// 浅拷贝:在堆内存中新开辟一个空间,创建一个新对象
// 拷贝原对象第一层基本数据类型的值和引用类型的地址。
// 第一层:最外面一层 {}最近的一层
// 1. 实现浅拷贝的方式
// 1. Object.assign({}, obj)
let obj2 = Object.assign({}, obj1)
// 第一层,属性值是基本(值)类型,拷贝前后两个对象相互不影响
obj2.name = 'Go'
// 第一层,如果属性值为引用类型 引用类型的值改变,会相互影响
obj2.book.price = '66'
console.log(obj2)
console.log(obj1)
console.log(obj2.name === obj1.name) // 基本数据类型 全等 ==> 值和数据类型
console.log(obj2.book === obj1.book) // true 引用类型,看地址,指向的是不是同一个对象
console.log(obj2.book.price === obj1.book.price)
javascript
// 2. 扩展(展开)运算符 Spread ...
let a = {
name: "JS",
book: {
title: "You Don't Know JS",
price: "169"
}
}
let b = {...a}
// console.log(b)
b.name = 'Go'
console.log(a) // 基本数据类型,相互不影响
console.log(b)
b.book.price = 88
console.log(a) // 引用数据类型,相互影响
console.log(b)
浅拷贝 ==> 对象 1. Object.assign() 2. 扩展运算符 const b = {...a} ==> 数组 1. arr.concat() 2. arr.slice()
javascript
// 浅拷贝:针对数组
// 1. arr.concat()
// 2. arr.slice()
// 3. Array.prototype.concat()
const arr1 = [1, 2, {user:'jl'}]
const arr2 = arr1.concat()
// arr2[0] = 1122
// console.log(arr1)
// console.log(arr2)
arr2[2].user = 'jg'
console.log(arr1)
console.log(arr2)
console.log('-----------')
// 4. Array.prototype.slice()
const arr3 = [1, 2, {user:'jl'}]
const arr4 = arr3.slice()
// console.log(arr3)
// console.log(arr4)
arr3[2].user = 'lianlian'
console.log(arr3)
console.log(arr4)
深拷贝
javascript
// 深拷贝:
// 在堆内存中开辟一个空间,创建一个新对象
// 递归的拷贝原对象的所有属性和方法,拷贝前后两个对象,相互不影响
// 递归拷贝:一层一层,每一层对象都新创建一个内存空间
let a1 = { b:{c:{}}}
let a2 = {...a1}
console.log(a2)
// 浅拷贝:新旧两个对象,如果有引用类型,共享同一块内存空间,相互影响
console.log(a1.b.c === a2.b.c) // true
// 深拷贝:新旧对象,不会共享内存空间
deepClone() // 假设这是一个实现深拷贝的方式
let a3 = deepClone(a1)
a3.b.c === a1.b.c // false
// 实现深拷贝的方式有三种
// 1. const res = JSON.parse(JSON.stringify(obj))
// 2. 使用一些js库,比如lodash _.cloneDeep()
// 3. 手写递归实现
JSON.parse(JSON.stringify(obj))
javascript
// 序列化 (Serialization)是将对象转换为可以存储或传输的形式的过程
// JSON.stringify() 也叫做JSON序列化
// JSON.parse() 反序列化
// 1. JSON.parse(JSON.stringify(obj))
// 1.1 JSON.stringify(obj) 把一个对象转为字符串了,
// 那么在内存中,对象有地址,字符串基本数据类型没地址,相互没关联了
// 1.2 再把转换后的字符串,又转为对象 (新开辟了一个空间)
const obj = {
name: 'zjl',
age: 18,
hobby:['dance','music'],
book:{
book_name:'Golang',
price:66
},
// test0:function(){},
// test1:undefined,
// test2:Symbol('id'),
// test3:new RegExp(/abc/, 'i'),
// test4:NaN,
// test5:Infinity,
// test6:new Date()
}
const res = JSON.parse(JSON.stringify(obj))
// console.log(res)
// res.hobby[0] = 'rap'
// res.book.price = 33
// console.log(obj)
console.log(res)
// 2. 它的缺陷
// 1. 拷贝对象的属性值 如果是 (function / undefined) / Symbol, 这个键值对丢失 ==> 重点记住
// 2. 如果拷贝对象的属性值是RegExp,会变成空对象{}
// 3. 如果是NaN,Infinity, 属性值会变成null
// 4. 如果拷贝日期对象,会变成日期字符串
lodash库
const res = _.cloneDeep(obj)
javascript
// 1. 引入js
<script src="./js/lodash.min.js"></script>
const obj = {
name: 'zjl',
age: 18,
hobby:['dance','music'],
book:{
book_name:'Golang',
price:66
},
test0:function(){},
test1:undefined,
test2:Symbol('id'),
test3:new RegExp(/abc/, 'i'),
test4:NaN,
test5:Infinity,
test6:new Date()
}
// 深拷贝 _是lodash的一个对象,里面有一个方法 cloneDeep()
const o = _.cloneDeep(obj)
console.log(o)
递归
递归:自己调用自己 有递 , 有归 递归也可以看作是一种循环(不断的自己调用自己),循环有终止条件,递归当然也需要终止条件 递归的终止条件,一般写在第二次调动之前, return可以终止函数的执行。
javascript
// 递归:自己调用自己 有递 , 有归
function fn(n) {
if (n === 1) {
return 1
}
return n + fn(n - 1)
}
// fn(3) ==> 3 + fn(2) ==> 3 + 2 + fn(1) ==> 3 + 2 + 1 ==> 6
// fn(5) ==> 5 + fn(4) ==> 5 + 4 + fn(3) ===> 5 + 4 + 3 + fn(2)
// ===> 5 + 4 + 3 + 2 + 1 15
// 调用
let res = fn(5)
console.log(res)
// 递归爆栈 (栈溢出)
// Uncaught RangeError: Maximum call stack size exceeded
// 栈 ==> 数据结构, 栈有深度, 如果超出了,就会栈溢出
// Last In First Out 后进先出
// let res2 = fn(1000000)
// JS代码,执行的时候,有一个执行栈,调用栈 Call Stack
javascript
// 试一试 递归
let i = 1
function count(){
console.log(`这是第${i}次调用`)
i++
if( i >= 100){ // 终止条件
return
}
count()
}
count()
// 练习,每隔一秒,递归。用setTimeout,模拟setInterval打印当前时间
function timer(){
const time = new Date().toLocaleString()
console.log(time)
setTimeout(timer, 1000)
}
timer()
手写深拷贝
javascript
const obj = {
name: 'zjl',
age: 18,
hobby:['dance','music'],
book:{
book_name:'Golang',
price:66
}
}
// 递归实现深拷
function deepClone(obj){
// 1. 如果是基本数据类型,直接返回
if (typeof obj !== 'object') return obj
// 2. 判断传入的是数组还是对象,在内存中新创建数组/对象
let newObj = Array.isArray(obj)? [] : {}
// 给新创建的对象添加属性和值,遍历传入过来的那个对象的属性,一个一个添加到新对象上
return newObj
}
const res = deepClone()
console.log(res)
todo02 浅拷贝中,目前是浅拷贝
javascript
// 递归实现深拷
function deepClone(obj){
// 1. 如果是基本数据类型,直接返回
if (typeof obj !== 'object') return obj
// 2. 判断传入的是数组还是对象,在内存中新创建数组/对象
let newObj = Array.isArray(obj) ? [] : {}
// 给新创建的对象添加属性和值,遍历传入过来的那个对象的属性,一个一个添加到新对象上
for(let key in obj){
// console.log(key) ==> 属性名 字符串 变量
// console.log(obj[key]) ==> 对应key的属性值
// 当我们的key为hobby时, obj['hobby'] ==> ['dance','music']
newObj[key] = obj[key]
// 因为 obj[key] 这个时候得到的是数组,把这个数组,赋值给左边,赋的是地址
// newObj['hobby'] = ['dance','music']
}
return newObj
}
const res = deepClone(obj)
console.log(res)
res.hobby[0] = 'rapper'
console.log(obj)
todo03 递归实现深拷贝
javascript
const obj = {
name: 'zjl',
age: 18,
hobby:['dance','music'],
book:{
book_name:'Golang',
price:66
}
}
// 深拷贝,在内存中开辟一个空间,创建一个新的对象, ===> 考虑数组和对象两种情况
// 递归实现深拷
function deepClone(obj){
// 1. 如果是基本数据类型,直接返回
if (typeof obj !== 'object') return obj
// 2. 判断传入的是数组还是对象,在内存中新创建数组/对象
let newObj = Array.isArray(obj) ? [] : {}
// 给新创建的对象添加属性和值,遍历传入过来的那个对象的属性,一个一个添加到新对象上
for(let key in obj){
if (typeof obj[key] === 'object'){
// 当key 取 hobby时, obj[key] 是 ['dance','music'] 把它传入到deepClone再拷贝
newObj[key] = deepClone(obj[key])
} else {
newObj[key] = obj[key]
}
}
return newObj
}
const res = deepClone(obj)
console.log(res)
res.hobby[0] = 'rapper'
console.log(obj)
javascript
function deepClone(obj){
// 1. 如果是基础类型,直接返回 => (拷贝的时候 newObj.name = '123' 直接添加)
if (typeof obj !== 'object') return obj
// 2. 如果是对象数组,内存中新创建对象/数组
let target = Array.isArray(obj) ? [] : {}
// 3. 遍历对象或数组,递归遍历
for(let key in obj){
// 给刚才创建的对象添加新的属性和属性值
target[key] = deepClone(obj[key])
}
// 4. 返回这个对象
return target
}
// 高级面试不够用~~~一般面试够用版
// 高级前端 还需掌握
// 1. 其他一些数据类型的判断 Date/Error 等
// 2. 对象的循环引用问题如何解决
this指向
javascript
// this 指向 ===> 指向 可以理解为 等于 代表谁
// this在定义的时候不能确定, 只有执行调用的时候才能确定.!!! 重要
// 背下来!
// 1. 全局作用域中 / 普通函数中 / 定时器里面 this 指向 window
// 1.1 全局作用域
console.log(this) // window
// 1.2 普通函数调用
function fn() {
console.log('大家吃早饭了嘛?')
console.log(this) // window
}
fn()
// 1.3 定时器里面
setTimeout(function(){
console.log(this) // window
}, 1000)
// 2.1 方法调用中, 谁调用这个方法, this指向谁
const obj = {
name: '小平',
age: 18,
sayHi: function(){
console.log(this)
}
}
obj.sayHi()
// 2.2 事件注册的时候, this指向被绑定的元素
const btn = document.querySelector('button')
btn.addEventListener('click', function(e){
console.log(this) // 绑定事件的元素
console.log(e.target) // 触发事件的元素
console.log(e.currentTarget) // 同this, 绑定事件的元素
})
console.log('-------------------------')
// 3. 构造函数中, this 指向的是 构造函数的实例
function Foo(name, age) {
this.name = name
this.age = age
console.log(this)
}
const person1 = new Foo('小红', 19)
const person2 = new Foo('小白', 18)
call改变this指向
javascript
const obj = {
msg: 'hello world'
}
function fn(x, y){
console.log(this)
// 如果我们让这个fn函数调用的时候,this指向obj
console.log(this.msg)
return x + y
}
// console.log(fn(1, 2))
// const res = fn.call(obj, 1, 2)
// fn.call()
const res = fn.call(obj, 1, 2)
console.log(res)
// 1. 改变this指向
// 2. 调用函数,函数立即执行,返回执行结果
// 3. 参数: 第一个参数 在函数执行的时候,让它内部的this指向哪个对象
// 后面是跟参数列表,原来fn函数参数是什么,就写什么
javascript
const obj1 = {
name:'hw',
getName(){
console.log(this)
console.log(this.name)
}
}
obj1.getName()
const obj2 = {
name:'iPhone'
}
console.log('-------------')
// 改变getName里面this指向,让this指向obj2
obj1.getName.call(obj2)
apply
javascript
const obj = {
msg: 'hello world'
}
function fn(x, y){
console.log(this)
// 如果我们让这个fn函数调用的时候,this指向obj
console.log(this.msg)
return x + y
}
// fn.call(obj, 1, 2)
const res = fn.apply(obj, [1, 2])
console.log(res)
fn.apply()
// 1. 改变this指向
// 2. 调用函数,立即执行
// 3. 参数,第一个,让fn函数执行时,this指向哪个对象
// 第二个,跟一个数组,数组中是原函数的参数
// fn.apply() 返回值就是函数的返回值
// call / apply 区别 ==> 他们都是改变this的指向,call接收参数列表,apply接收数组
bind
javascript
const obj = {
msg: 'hello world'
}
function fn(x, y){
console.log(this)
// 如果我们让这个fn函数调用的时候,this指向obj
console.log(this.msg)
return x + y
}
// fn.bind(obj, 参数列表)
// 1. 改变this指向
// 2. 有返回值,返回值是一个函数, 这个函数中的this就是我们传入的第一个参数
// 3. bind不是立即执行的,需要我们手动调用
const fun = fn.bind(obj, 1, 2)
console.log(fun)
// const fun = function(x, y){
// console.log(this)
//
// console.log(this.msg)
// return x + y
// }
fun()
/* ==================== 面试题 ===================== */
// call / apply / bind 区别
// 1. 都是改变this的指向
// 2. call 接收参数列表,apply 接收数组
// 3. call / apply 是立即执行的,bind返回一个函数,需要手动调用
/* ==================== end ===================== */
防抖/节流
javascript
箭头函数 , 剩余参数 return返回值 , this指向
call / apply / bind
作用域
闭包
定时器
清除定时器
api事件绑定 ==> addEventListener