web 面试高频考点 —— JavaScript 篇(一)变量类型和计算、原型和原型链、作用域和闭包

系列文章目录



变量类型和计算

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 函数形式

异步和同步

异步:callback 回调函数

    console.log(100)
    setTimeout(() => {
    
    
        console.log(200)
    }, 100)
    console.log(300)
    // 输出顺序:100 300 200

同步:按顺序执行,前面没执行完后面的不会执行

    console.log(100)
    alert(200)
    console.log(300)

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_45902692/article/details/126077498