Skip to content

JS基础语法

变量类型计算

题目

  1. typeof 能判断哪些类型
  2. 何时使用 ===何时使用 ==
  3. 值类型和引用类型的区别
    • 堆栈图
  4. 手写深拷贝
    • 注意判断值类型和引用类型
    • 注意判断是数组还是对象
    • 递归

知识点

变量类型

  • 值类型 vs 引用类型

    // 值类型 
    let a = 100
    let b = a
    a = 200
    console.log(b)  // 100
    
    值类型存储

    // 引用类型
    let a = { age: 20 }
    let b = a
    b.age = 21
    console.log(a.age)  // 21
    
    引入类型存储

    // 常见值类型 
    let a // undefined
    const s = 'abc'
    const n = 100
    const b = true
    const s = Symbol('s')
    // 常见引用类型
    const obj = { x: 100}
    const arr = ['a', 'b', 'c']
    const n = null // 特殊引用类型,指针指向为空地址
    // 特殊引用类型,但不用于存储数据,所以没有“拷贝、复制函数”这一说
    function fn() {}
    
  • typeof 运算符

    // 判断所有值类型 
    let a                 typeof a    // 'undefined'
    const str = 'abc'     typeof str  // 'string'
    const n = 100         typeof n    // 'number'
    const b = true        typeof b    // 'boolean'
    const s = Symbol('s') typeof s    // 'symbol'
    
    // 判断函数
    typeof console.log     // 'function'
    typeof function () {}  // 'function'
    
    // 识别引用类型(不能再继续识别)
    typeof null        // 'object'
    typeof ['a', 'b']  // 'object'
    typeof { x: 100 }  // 'object'
    

  • 深拷贝

    /**
    * 深拷贝
    */
    const obj1 = {
        age: 20,
        name: 'xxx',
        address: {
            city: 'beijing'
        },
        arr: ['a', 'b', 'c', 'd']
    }
    
    const obj2 = deepClone(obj1)
    obj2.address.city = 'shanghai'
    console.log(obj1.address.city)
    
    /**
    * obj 要拷贝的对象
    */
    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(obj)){
                // 递归调用
                result[key] = deepClone(obj[key])
            }
        }
        return result
    }
    

变量计算

  • 类型转换

    // 字符串拼接
    const a = 100 + 10    // 110
    const b = 100 + '10'  // '10010'
    const c = true + '10' // 'true10'
    
    // == 
    100 == '100'   // true
    0 == ''        // true
    0 == false     // true
    false == ''    // true
    null == undefined // true
    
    // 除了 == null之外,其他一律用 === ,例如:
    const obj = { x: 100 }
    if (obj.a == null) { }
    // 相当于:if (obj.a === null || obj.a === undefined) { }
    
    // if语句和逻辑运算
    truly 变量 !!a === true 的变量
    falsely 变量 !!a === false 的变量
    // 以下是 falsely 变量。除此之外都是 truly 变量
    !!0 === false
    !!NaN === false
    !!'' === false
    !!null === false
    !!undefined === false
    !!false === false
    
    if语句
    if语句 实际上判断的就是 truly 变量 和 falsely 变量,并不是非要判断true false

    逻辑判断
    逻辑判断 “&& 返回 falsely 变量”。 “|| 返回 truly 变量”

原型和原型链

题目

  1. 如何准确判断一个变量是不是数组?
    • a instanceof Array
  2. 手写一个简易的jQuery,考虑插件和扩展性
    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)
            })
        }
    }
    
    // const $p = new jQuery('p')
    // $p.get(1)
    // $p.each((elem) => console.log(elem.nodeName))
    // $p.on('click', () => alert('clicked'))
    
    // 插件
    jQuery.prototype.dialog = function (info) {
        alert(info)
    }
    // $p.doalog('abc')
    
    // 复写 (造轮子)
    class myJQuery extends jQuery {
        constructor(selector) {
            super(selector)
        }
        // 扩展自己的方法
        addClass(className) {
            // ...
        }
        style(data) {
            // ...
        }
    }
    
  3. class的原型本质,怎么理解?
    • 原型和原型链的图示
    • 属性和方法的执行规则

知识点

class和继承

  • class

    /**
     *  - constructor
     *  - 属性
     *  - 方法
     */
    // 类
    class Student {
    constructor(name, number){
        this.name = name
        this.number = number
    }
    
    sayHi() {
        console.log(
            `姓名 ${this.name} , 学号 ${this.number}`
            )
        }
    }
    
    // 通过类 new 对象/实例
    const xialuo = new Student('夏洛' 10000)
    console.log(xialuo.name)
    console.log(xialuo.number)
    xialuo.sayHi()
    

  • 继承

    /**
    *  - extends
    *  - super
    *  - 扩展或重写方法
    */
    // 父类
    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}`
            )
        }
    }
    
    // 通过类 new 对象/实例
    const xialuo = new Student('夏洛' 10000)
    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()
    

类型判断instanceof

xialuo instanceof Student   // true
xialuo instanceof People    // true
xialuo instanceof Object    // true

[] instanceof Array       // true
[] instanceof Object      // true
{} instanceof Object      // true

原型和原型链

原型
// class 实际上时函数,可见是语法糖
typeof Student  // 'function'
typeof People   // 'function'

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

原型

原型
  • 原型关系
    • 每个 class 都有显示原型 prototype
    • 每个实例都有隐式原型 __proto__
    • 实例的 __proto__ 指向对应 classprototype
  • 基于原型的执行规则
    1. 获取属性
      xialuo.name
      
      或执行方法
      xialuo.sayhi()
      
    2. 先在自身属性和方法寻找
    3. 如果找不到则自动去 __proto__中查找
原型链
  console.log(Student.prototype.__proto__)
  console.log(People.prototype)
  console.log(People.prototype === Student.prototype.__proto__) // true

原型链

原型链

原型链最底部

到 Object 截止。Object 的 __proto__ 指向null

作用域和闭包

题目

  1. this的不同应用场景,如何取值?
    • 注意:情况比较多,多看笔记
  2. 手写bind函数
    function fn1(a, b) {
        console.log('this', this)
        console.log(a, b)
        return 'this is fn1'
    }  
    const fn2 = fn1.bind({x: 100}, 10, 20)
    const res = fn2()
    console.log(res)
    
    // 模拟 bind
    Function.prototype.bind1 = function() {
        // 将参数拆解为数组
        const args = Array.prototype.slice.call(arguments)
    
        // 获取 this (数组第一项)
        const t = args.shift()
    
        // fn1.bind(...) 中的 fn1
        const self = this
    
        // 返回一个函数
        return function () {
            return self.apply(t, args)
        }
    }
    
  3. 实际开发中闭包的应用场景,举例说明

    • 隐藏数据
    • 如做简单的cache工具
      // 闭包隐藏数据,只提供 API
      function createCache() {
          const data = {} // 闭包中的数据,被隐藏,不被外界访问
          return {
              set: function (key, val) {
                  data[key] = val
              },
              get: function(key) {
                  return data
              }
          }
      }
      const c = createCache()
      c.set('a', 100)
      console.log(c.get('a'))
      
  4. 代码题

    // 创建 10 个 `<a>`标签,点击的时候弹出来对应的序号
    let i, a
    for (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)
    }
    // 解答:块级作用域
    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)
    }
    

知识点

作用域

值类型存储

  1. 全局作用域
    • window对象
    • document对象
  2. 函数作用域
    let a = 0
    function fn1() {
        let a1 = 100
    
        function fn2() {
            let a2 = 200
    
            function fn3 () {
                let a3 = 300
                return a + a1 + a2 + a3
            }
        }
    }
    
    // 函数内声明的变量
    // a  a1  a2 为自由变量
    
  3. 块级作用域(ES6新增)
    if (true) {
        let x = 100
    }
    console.log(x) // 会报错
    

自由变量

  1. 一个变量在当前作用域没有定义,但被使用了
  2. 向上级作用域,一层一层依次寻找,直到找到为止
  3. 如果到全局作用域都没找到,则报错 xx is not defined

闭包

  • 作用域应用的特殊情况,有两种表现:
    1. 函数作为参数被传递
    2. 函数作为返回值被返回
      // 函数作为返回值
      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(传入什么绑定什么)
  • 作为对象方法被调用(返回对象本身)
  • 在class方法中调用(返回当前函数本身)
  • 箭头函数(找上级作用域的this值)
    // this取什么值是在函数执行的时候确认的,不是在函数定义时候确认的
    function fn1() {
        console.log(this)
    }
    fn1() // window
    
    fn1.call({ x: 100 }) // { x: 100 }
    
    const fn2 = fn1.bind({ x: 200 })
    fn2() // { x: 200 }
    
    const zhangsan = {
        name: '张三',
        sayHi() {
            // this 即当前对象
            console.log(this)
        },
        wait() {
            // 对象方法
            setTimeout(function() {
                // this === window
                console.log(this)
            })
    
            // 箭头函数 取上级作用域的值
            setTimeout(() => {
                // this 即当前对象
                console.log(this)
            })
        }
    }
    

异步

题目

  1. 同步和异步的区别是什么?
    • 基于JS是单线程语言
    • 异步不会阻塞代码执行
    • 同步会阻塞代码执行
  2. 手写用Promise加载一张图片
    function loadImg(src) {
        return new Promise((resolve, reject) => {
            const img = document.createElement('img')
            img.onload = () => {
                resolve(img)
            } 
            img.onerror = () => {
                const err = new Error(`图片加载失败 ${src}`)
                reject(err)
                // reject(new Error(`图片加载失败 ${src}`))
            }
            img.src = src
        })
    }
    
    // 使用
    const url = 'https://111.png'
    loadImg(url).then(img => {
        console.log(img.width)
        return img
    }).then(img => {
        console.log(img.height)
    }).catch(err => console.error(err))
    
    // 多个图片使用
    const url1 = 'https://111.png'
    const url2 = 'https://222.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(err => console.error(err))
    
  3. 前端使用异步的场景有哪些?
  4. 场景题
    // setTimeout 笔试题
    console.log(1)
    setTimeout(function () {
        console.log(2)
    }, 1000)
    console.log(3)
    setTimeout(function () {
        console.log(4)
    }, 0)
    console.log(5)
    
    // 1 3 5 4 2
    

知识点

单线程和异步

  • JS是单线程语言,只能同时做一件事儿
  • 浏览器和 nodejs 已支持 JS 启动进程,如 Web Worker
  • JS 和 DOM 渲染共用同一个线程,因为 JS 可修改 DOM 结构
  • 遇到等待(网络请求,定时任务)不能卡住
  • 需要异步
  • 回调 callback 函数形式

应用场景

  1. 网络请求,如ajax图片加载
  2. 定时任务,如setTimeout
    // ajax
    console.log('start')
    $.get('./data1.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(100)
    setTimeout(function () {
        console.log(200)
    }, 1000)
    console.log(300)
    
    // setInterval
    console.log(100)
    setInterval(function () {
        console.log(200)
    }, 1000)
    console.log(300)
    

callback hell 和 Promise

// callback hell 回调地域
// 获取第一份数据
$.get(url1, (data1) => {
    console.log(data1)

    // 获取第二份数据
    $.get(url2, (data2) => {
        console.log(data2)

        // 获取第三份数据
        $.get(url3, (data3) => {
            console.log(data3)

            // 还可能获取更多的数据
        })
    })
})

// Promise
function getData(url) {
    return new Promise((resolve, reject) => {
        $.ajax({
            url,
            success(data) {
                resolve(data)
            },
            error(err) {
                reject(err)
            }
        })
    })
}

const url1 = '/data1.json'
const url2 = '/data2.json'
const url3 = '/data3.json'
getData(url1).then(data1 => {
    console.log(data1)
    return getData(url2)
}).then(data2 => {
    console.log(data2)
    return getData(url3)
}).then(data3 => {
    console.log(data3)
}).catch(err => console.error(err))