【前端面试基础】(二)变量、作用域、原型、闭包

这里补充一个小小的知识点:js中数据类型分为基本数据类型引用数据类型

  • 基本数据类型 :Number,String,Boolean,null,undefined,Symbol(ES6新增,表示独一无二的值),Bigint(比Number数据类型支持的范围更大的整数值,整数溢出不是问题)
  • 引用数据类型:Object,Array,Function,Date

typeof能判断哪些类型

  • 能识别所有的值类型(输出类型字符串如:"string")
  • 识别函数(输出'function')
  • 判断是否是引用类型(只输出object,不可再细分)
csharp 复制代码
typeof null //'object'
typeof ['a','b'] //'object'
typeof {x:100}  //'object'

typeof可以区分除了Null类型以外的其他基本数据类型,以及从对象类型中识别出函数(function)。

其返回值有:numberstringbooleanundefinedsymbolbigintfunctionobject

其中, typeof null返回 "object"

如果要识别null,可直接使用===全等运算符来判断。

javascript 复制代码
typeof 1 // 'number'
typeof '1' // 'string'
typeof true // 'boolean'
typeof undefined // 'undefined'
typeof Symbol() // 'symbol'
typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof console // 'object'
typeof console.log // 'function'
  1. instanceof

instanceof一般是用来判断引用数据类型,但不能正确判断基本数据类型,根据在原型链中查找判断当前数据的原型对象是否存在返回布尔类型。

javascript 复制代码
1 instanceof Number; // false
true instanceof Boolean; // false
'str' instanceof String; // false
[] instanceof Array; // true
function(){} instanceof Function; // true
{} instanceof Object; // true
let date = new Date();
date instance of Date; // true
  1. Object.prototype.toString
javascript 复制代码
Object.prototype.toString({}) // "[object Object]"
Object.prototype.toString.call({}) // 同上结果,加上call也ok
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('1') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(function () {}) // "[object Function]"
Object.prototype.toString.call(null) //"[object Null]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call(/123/g) //"[object RegExp]"
Object.prototype.toString.call(new Date()) //"[object Date]"
Object.prototype.toString.call([]) //"[object Array]"
Object.prototype.toString.call(document) //"[object HTMLDocument]"
Object.prototype.toString.call(window) //"[object Window]"
  1. Array.isArray

Array.isArray(value)可以判断 value 是否为数组。

javascript 复制代码
Array.isArray([]); // true
Array.isArray({}); // false
Array.isArray(1); // false
Array.isArray('string'); // false
Array.isArray(true); // false

常见值类型:

  • undefined
  • String字符串
  • Number
  • Boolean
  • Symbol(ES6)新增

常见引用类型:

  • 对象Object

  • Array数组

  • null (特殊引用类型,指针指向为空地址)

  • function 函数(特殊引用类型,但不用于存储数据)

值类型和引用类型的区别

手写深拷贝

深拷贝 :创建一个新的对象来承接原对象中的原始值,拷贝得到的对象不会受原对象的影响。

【js手写】浅拷贝与深拷贝 - 掘金
面试如何写出一个满意的深拷贝(适合初级前端)

javascript 复制代码
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 不是原型的属性,hasOwnProperty判断自身是否有指定的属性,因为for...in会遍历原型上的属性
        这行代码的作用是确保在进行深度克隆时,只复制对象自身拥有的属性,而不会复制从原型链继承而来的属性。
        if (obj.hasOwnProperty(key)) {
            // 递归
            result[key] = deepClone(obj[key]);
        }
    }
​
    // 返回结果
    return result;
}

4. 三种发生类型转换的情况

  • 字符串拼接

  • ==运算符,==会尝试类型转换之后再判断相等

  • if判断语句,实际是执行两次非运算判断是否是turly变量

    以下是falsely变量(除此之外都是 truly 变量):

字符串拼接
ini 复制代码
const a =100+'10'    //'10010'
const b =true+'10'    // 'true10'

何时使用===?何时使用==?

在JavaScript中,===== 是用于比较两个值的运算符。

  1. ===(严格相等):

    • === 用于严格比较两个值,包括它们的类型和值。
    • 只有当两个值的类型和值完全相同时,=== 才会返回 true
    • 例如:5 === 5 会返回 true,但是 5 === '5' 会返回 false,因为它们的类型不同。
  2. ==(相等):

    • == 也用于比较两个值,但它会在必要时进行类型转换,再进行比较。
    • 这意味着在使用 == 时,如果两个值的类型不同,JavaScript 会尝试将它们转换成相同的类型,然后再比较。
    • 例如:5 == '5' 会返回 true,因为 JavaScript 将字符串 '5' 转换成了数字 5,然后进行比较。

建议使用===的情况

  • 当你想要确保比较的值既具有相同的类型又具有相同的值时,使用 === 是明智的选择。
  • 在大多数情况下,推荐使用 ===,因为它避免了类型转换可能导致的意外行为。

建议使用==的情况

  • 当你明确地想要允许 JavaScript 进行类型转换时,可以使用 ==。这可能在一些特定的情况下会很有用,但要小心,因为它可能会导致一些意外的行为。

总的来说,使用 === 通常更加安全和可靠,因为它不会进行类型转换,只有在类型和值都相同时才返回 true

==运算符
ini 复制代码
100=='100'  //true
0==''  //true
0 ==false //true
false == '' //true
null == undefined  //true
ini 复制代码
      //除了 == null 之外,其他都一律用 ===
      //    eg:
      const obj = { x: 100 };
      if (obj.a == null) {
      }
      //相当于
      if (obj.a === null || obj.a === undefined) {
      }
if语句和逻辑运算
  • if判断语句,实际是执行两次非运算判断是否是turly变量

    以下是falsely变量(除此之外都是 truly 变量):

    ini 复制代码
    !!0 === false
    !!NaN === false
    !!'' === false
    !!null === false
    !!undefined === false
    !!false === false

如何判断一个变量是不是数组

  • a instanceof Array
  • a.__proto__ === Array.prototype
ini 复制代码
let arr = [1, 2, 3];
let notArr = 'Hello';

console.log(arr.__proto__ === Array.prototype);   // true
console.log(notArr.__proto__ === Array.prototype); // false
  • Array.isArray() (推荐)
  • Object.prototype.toString.call() 方法
javascript 复制代码
javascriptCopy code
let arr = [1, 2, 3];
let notArr = 'Hello';

console.log(Object.prototype.toString.call(arr) === '[object Array]');   // true
console.log(Object.prototype.toString.call(notArr) === '[object Array]'); // false

这种方法利用了 Object.prototype.toString 方法返回一个描述了对象类型的字符串,例如 [object Array] 表示数组类型。

class

typescript 复制代码
// 类
class Student {
    constructor(name, number) {
        this.name = name
        this.number = number
        // this.gender = 'male'
    }
    sayHi() {
        console.log(
            `姓名 ${this.name} ,学号 ${this.number}`
        )
        // console.log(
        //     '姓名 ' + this.name + ' ,学号 ' + this.number
        // )
    }
    // study() {

    // }
}

// 通过类 new 对象/实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()

const madongmei = new Student('马冬梅', 101)
console.log(madongmei.name)
console.log(madongmei.number)
madongmei.sayHi()

class继承

typescript 复制代码
// 父类
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}`)
    }
}

// 实例
const xialuo = new Student('夏洛', 100)
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()

原型

原型关系

  • 每个class都有显示原型 prototype
  • 每个实例都有隐式原型 proto
  • 实例的_proto_ 指向对应class 的prototype
javascript 复制代码
// class实际上是函数,可见是语法糖
typeof People //'function'
typeof Student //'function'



// 隐式原型和显示原型
console.log(xialuo._proto_);
console.log(Student.prototype);
console.log(xialuo._proto_ === Student.prototype);  //true

原型链

一文搞懂JS原型与原型链(超详细,建议收藏)

2022年元旦,我终于搞懂了原型和原型链

JS原型与原型链

javascript 复制代码
console.log(Student.prototype._proto_);
console.log(People.prototype);
console.log(People.prototype === Student.prototype._proto_);  //true

instanceof本质也是基于原型链

a instanceof B,本质是如果a沿着原型链能够找到B.prototype,那么就返回true,否则返回false。因此,以上第一二种方法如果修改了原型链,则判断出错。

手写instanceof

javascript 复制代码
const instanceOf = (A, B) => {
    let p = A;
    while (p) {
        if (p === B.prototype) {
            return true;
        }
        p = p.__proto__;
    }
    return false;
}
​
console.log(instanceOf(1, Number)); // true
console.log(instanceOf([], Array)); // true
console.log(instanceOf([], Object)); // true

class的本质,如何理解

class是一种语法糖

  • class是ES6的新特性,用来定义一个类。
  • class有构造方法constructor、属性、方法
  • 子类可以通过extends继承父类,通过super调用父类构造方法

class本质也是通过原型来实现的

比如定义一个类Person,定义一个类Student继承自Person,通过Student创建一个实例zhangsan。

zhangsan.__proto__ === Student.prototypeStudent.prototype.__proto__ === Person.prototype

有关原型链,可以参考我的另一篇文章:JS原型与原型链

作用域和自由变量

作用域

  • 全局作用域
  • 函数作用域
  • 块级作用域(ES6新增)

自由变量

自由变量的值:现在本级作用域查找,如果未找到,向上级作用域依次查找,直到找到为止,如果到全局作用域都未找到,则报错 xxx is not defined

什么是闭包?闭包会用在哪里?

闭包

  • 作用域应用的特殊情况,有两种表现:

  • 函数作为参数被传递

    scss 复制代码
    function print(fn) {
        let a = 200;
        fn();
    }
    let a = 100;
    function fn() {
        console.log(a);
    }
    print(fn); // 打印100
  • 函数作为返回值被返回

    ini 复制代码
    function create() {
        let a = 100;
        return function() {
            console.log(a);
        }
    }
    let fn = create();
    let a = 200;
    fn(); // 打印100

闭包:所有自由变量的查找,是在函数定义的位置向上级作用域查找,不是在函数执行的位置!!

实际开发中闭包的应用场景

  • 设置私有变量
  • 函数节流、防抖
  • 循环中拿到正确的值
  1. 隐藏数据,做应该简单的cache工具
kotlin 复制代码
// 闭包隐藏数据,只提供 API
function createCache() {
    const data = {} // 闭包中的数据,被隐藏,不被外界访问
    return {
        set: function (key, val) {
            data[key] = val
        },
        get: function (key) {
            return data[key]
        }
    }
}

// data.b = 200// 不能访问createCache,会出现  Uncaught ReferenceError: data is not defined

const c = createCache()
c.set('a', 100)
console.log(c.get('a'))

this 的不同应用场景,如何取值?

this的取值,是在函数执行的时候确定的,不是在函数定义的时候确定的。

  • 全局作用域或者普通函数中this指向全局对象window(注意定时器中的this指向window)
  • 对象调用方法时,指向调用方法的对象本身
  • 构造函数中this指向构造函数实例
  • 箭头函数的this指向它声明时作用域的this
  • 定时器中的this指向window(但如果定时器回调是使用箭头函数声明,则指向声明时作用域的this)
  • 事件绑定方法的回调,this指向绑定对象
  • 使用call、apply、bind改变this指向,传入什么,this就绑定什么
javascript 复制代码
const zhangsan = {
    name: '张三',
    sayHi() {
        // this 即为当前对象
        console.log(this);
    },
    wait() {
        setTimeout(function () {
            // this === window
            console.log(this);
        })
    }
}

箭头函数的this会寻找上级作用域的this的指向

javascript 复制代码
const zhangsan = {
    name: '张三',
    sayHi() {
        // this 即为当前对象
        console.log(this);
    },
    wait() {
        setTimeout(() => {
            // this 即为当前对象
            console.log(this);
        })
    }
}
javascript 复制代码
class People {
    constructor(name) {
        this.name = name
        this.age = 20
    }
    sayHi() {
        console.log(this);
    }
}


const zhangsan = new People('张三')
zhangsan.sayHi()  //zhangsan对象

9. 手写bind函数

前期知识

javascript 复制代码
// 模拟 bind
Function.prototype.mybind = function () {
    // 将参数解析为数组
    const args = Array.from(arguments);
    //方法二
    //const args = Array.prototype.slice.call(arguments)
​
    // 获取 this(第一个参数,即数组第一项)---取出数组的第一项,数组剩余的就是传递的参数
    const t = args.shift();
​
    // 此时this是fn1.bind(...)中的 fn1 
    const self = this; //当前函数
​
    // 返回一个函数
    return function() {
        // 执行原函数,并返回结果
        return self.apply(t, args)
    }
}
相关推荐
腾讯TNTWeb前端团队41 分钟前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
uhakadotcom4 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
范文杰4 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪4 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪4 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy5 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
快速开始使用 n8n
后端·面试·github
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom6 小时前
React与Next.js:基础知识及应用场景
前端·面试·github