Js常见问题

128 阅读5分钟

typeof运算符

1.识别所有值类型
2.识别函数
3.判断是否是引用类型(不可再细分)
instanceof 类型判断

手写js深拷贝

function deepClone(
    //基本数据类型或null 直接返回
    if(typeof obj !== 'object' || obj === null){
        return obj
     }
    //初始化返回结果
    let result;
    //引用数据类型
    result = obj instanceof Array?[]:{}
    
    for(const key in obj){
        //保证key不是原型的属性
        if(obj.hasOwnProperty(key)){
            //递归调用
            result[key] = deepClone(obj[key])
        }
    }
    return result
}

原型和原型链

js的class实际上是函数,语法糖
隐式原型和显式原型
    bob.__proto__ ==== Student.prototype
    每个class都有显式原型prototype
    每个实例都有隐式原型__proto__
    实例的__proto__指向对应的class的prototype 
基于原型的执行规则
    先在自身属性和方法寻找
    如果找不到则自动去__proto__去寻找
instanceof 是基于原型链实现的    

手写简易jQuery考虑插件和扩展性

class JQuery {
    constructor(selector) {
        const res = document.querySelectorAll(selector)
        const length = res.length
        for (let i = 0; i < length; i++) {
            this[i] = res[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(ele => {
            ele.addEventListener(type, fn, false)
        })
    }
    ...
    //扩展很多DOM API
} 
//插件  可以往JQuery的原型链上添加
JQuery.prototype.dialog = function(info){
    alert(info)
}

//扩展性  即造轮子
class newJQuery extends JQuery{
    construct(selector){
        super(selector)
    }
    
    //扩展自己的方法
    
    addClass(className){
        ....
    }
    style(data){
        ...
    }

}

作用域和闭包

作用域
1.全局作用域
2.函数作用域
3.块级作用域
自由变量
1.一个变量在当前作用域没有定义,但被使用了
2.向上级作用域,一层一层依次寻找,直至找到为止
3.如果到全局作用域都没找到,则报错xx is not defined
闭包
自由变量的查找,是在函数定义的地方,向上级作用域查找 (注意:不是执行的地方)
//函数作为返回值
function create() {
   const a = 100
   return function() {
       console.log(a)
   }
}
const a = 200
const fn = create()
fn()      输出:100

//函数作为参数
function print(fn) {
   const a = 200
   fn()
}
const a = 100

function fn() {
   console.log(a)
}
print(fn)  输出:100

this的场景 和指向

场景
1.作为普通函数
2.使用call apply bind  可以改变this指向
    bind会返回一个新的函数
3.作为对象方法被调用
4.class方法中调用
5.箭头函数
指向
this的指向是在函数执行的时候确认的,不是定义的时候

手写bind函数

function fn1(a, b, c) {
   console.log('this', this)
   console.log(a, b, c)
   return 'this is fn1'
}
console.log('----------')
fn1(10, 20, 30)     //this指向window
console.log('----------')

console.log('----------')
const fn2 = fn1.bind({  //this指向{x:100}
    x: 100
}, 10, 20, 30)
const res = fn2()   
console.log(res)
console.log('----------')

//自己写一个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)
    }
}
console.log('----------')
const fn3 = fn1.bind1({  //this指向{x:100}
    x: 100
}, 10, 20, 30)
const result = fn3()   
console.log(result)
console.log('----------')

实际开发中闭包的应用

1.隐藏数据
//闭包隐藏数据,只提供API
function createCache() {
  const data = {} //闭包中的数据,被隐藏  不被外界访问
  return {
      set: (key, value) => {
              data[key] = value
      },
      get: (key) => {
              return data[key]
          }
     }
}

const c = createCache()
c.set('a', 100)
console.log(c.get('a'))   //data.a访问不到
2.创建10个a标签,点击弹出序号
//形成块级作用域   如果用var就会出问题
for (let i = 0; i < 10; i++) {    
   let a = document.createElement('a')
   a.innerHTML = i
   
   a.style.display = 'block'
   a.addEventListener('click',function(){
     alert(i)
   }, false)
   document.body.appendChild(a)
}

异步和单线程

JS是单线程语言,只能同时做一件事
浏览器和nodejs已支持JS启动进程,如Web Worker
JSDOM渲染共用同一个线程,因为JS可修改DOM结构
   遇到等待(网络请求,定时任务)不能卡住
   需要异步
   回调函数callback
同步和异步
    基于JS是单线程语言
    异步不会阻塞代码执行
    同步会阻塞代码执行
回调地狱 Promise解决回调地狱
手写用Promise加载一张图片
function loadImg(src){
    return new Promise((resolve,reject) => {
        const img = document.createElement('img')
        img.onload = () => {
            resolve(img)
        }
        img.onerror = () => {
            const error = `加载失败 - ${src}` 
            reject(error)
        }
        img.src = src
    })
}
-----------------------------------------
const src = 'https://dss2.bdstatic.com/lfoZeXSm1A5BphGlnYG/skin/493.jpg?2%22);'
loadImg(src).then(res => {
    const img = res
    document.body.appendChild(img)
}).catch(err => {
    console.log(err)
})
前端使用异步的场景有哪些
网络请求  如ajax 图片加载
定时任务  如setTimeout

DOM 树结构

获取DOM节点
document.getElementById
document.getElementByTagName
document.getElementsByClassName
document.querySelector
document.querySelectorAll
property和attribute
property:修改对象属性,不会体现到html结构中
attribute:修改html属性,会改变html结构
两者都有可能引起DOM重新渲染
DOM结构操作
新增/插入节点
获取子元素列表,获取父元素 
    box.childNodes  box.parentNode
删除节点  
    removeChild
DOM性能
避免频繁的DOM操作
对DOM查询做缓存
将频繁操作改为一次性操作
    

1.png

BOM

navigator  history  screen  location

事件绑定 事件冒泡 事件代理

event.preventDefault()  //阻止事件默认行为
事件冒泡的流程:
    基于DOM树型结构
    事件会顺着触发元素往上冒泡
    应用场景:代理
事件代理
    代码简洁  减少浏览器内存占用
无限下拉图片列表,如何监听每个图片的点击
    事件代理
    用e.target获取触发元素
    用matches来判断是否是触发元素

跨域

什么是跨域(同源策略)
ajax请求时,浏览器要求当前网页和server必须同源(安全)
同源:协议、域名、端口 
加载图片、css、js可无视同源协议 (比如cdn  jsonp)

<img />可用于统计打点,可使用第三方统计服务

所有的跨域,都必须经过server端允许和配合
未经server端允许就实现跨域,说明浏览器有漏洞,危险信号
JSONP
<script>可绕过跨域限制
服务器可以任意动态拼接数据返回
所以,<script>就可以获得跨域的数据只要服务端愿意返回

2.png

3.png

CORS(服务端支持)

4.png

手写一个简易的ajax

function ajax(url){
    return new Promise((resolve,reject) => {
        const xhr = new XMLHttpRequest()
        xhr.open('GET',url,true)
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4){
                if(xhr.status === 200){
                    resolve(
                      JSON.parse(xhr.responseText)
                    )
                }else if(xhr.status === 404){
                    reject(new Error('404 Not Found'))
                }
            }
        }
        xhr.send(null)
    })
}

ajax常用工具

JQuery Fetch Axios

Cookie SessionStorage LocalStorage

Cookie
存储大小 最大4kb
http请求时需要发送到服务端,增加请求数据量
document.cookie修改   用于浏览器和server通讯
SessionStorage LocalStorage
H5专门为存储而设计,最大可存5M
API简单易用 setItem  getItem
不会随着http请求被发送出去
LocalStrorage:数据会永久存储,除非手动删除
SessionStorage:数据只存在于当前会话,浏览器关闭则清空

手写防抖和节流

防抖
监听一个输入框,文字变化后触发change事件
直接用keyup事件,则会频发触发change事件
防抖:用户输入结束或暂停时,才触发change事件
const inputEl = document.getElementById('input');
inputEl.addEventListener('keyup', debouce(function(){
    console.log(input.value)
},300), false)


//防抖
function debounce(fn,delay = 500){
    //timer是闭包中的
    let timer
    return function(){
        if(timer){
            clearTimeout(timer)
        }
        timer = setTimeout(()=>{
            fn.apply(this,arguments)
        },delay)
    }
}
节流
   拖拽一个元素时,要随时拿到该元素被拖拽的位置
   直接用drag事件,则会频发触发,很容易导致卡顿
   节流:无论拖拽速度多快,都会每隔100ms触发一次
//节流
function throttle(fn, delay = 100) {
    let timer
    return function() {
        if (timer) {
            return
        }
        timer = setTimeout(function() {
            fn.apply(this, arguments)
            timer = null
        }, delay)
    }
}
const divEl = document.getElementById('div')

//元素的dragable属性需设置为true
divEl.addEventListener('drag',throttle(function(e){
    console.log(e.offsetX,e.offsetY)
},200),false)