Skip to content

继承

javascript
// 类:抽象的,泛指的,一个大类    ===> 构造函数可以当做一个类
// 面向对象的三大特征:封装,继承,多态

// 继承:子类继承(拥有)父类的属性和方法

// 实现继承的方式~~~ 八股文
/* ==================== start ===================== */
// 1. 原型链继承
// 2. 借用构造函数(经典继承)
// 3. 组合继承
// 4. 原型式继承
// 5. 寄生式继承
// 6. 寄生式组合继承
/* ==================== end ===================== */

// ES6.
// 7. extends 继承 ===> 语法糖 --> 寄生式组合继承

function Parent(name){
    this.name = name 
}

function Child(name){
    this.name = name 
}

原型链继承

image.png

核心:让子类的原型等于父类构造函数的实例 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

数据类型

image.pngimage.png

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, 所以需要用到浅拷贝、深拷贝

浅拷贝

image.png

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)

深拷贝

image.png

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