JS与html重要知识万字总结(持续更新

339 阅读32分钟

事件循环机制(Event Loop)

  • 什么是事件循环机制
  1. 执行js代码时,分为同步任务和异步任务。
  2. 同步任务进入调用栈执行,执行完以后从调用栈中弹出。异步任务的回调函数放入由webAPI提供的单独运行空间中。
  3. 异步任务中的回调函数等待合适的时间以后进入任务队列。宏任务进入宏任务队列,微任务进入微任务队列。
  4. 当调用栈空了以后,检查任务队列中是否有任务,先检查微任务队列,如果有就依次压入调用栈中执行。
  5. 微任务队列空了以后进行UI rendering。
  6. 检查宏任务队列中是否有任务,压入调用栈中执行。
  7. 当调用栈再次空了以后,不断检查任务队列中是否有任务需要执行,形成事件循环机制。

宏任务与微任务

  • 常见的宏任务 setTimeOut,SetInterval,Dom事件,Ajax请求
  • 常见的微任务 Promise.then,async await,nextTick

setTimeOut不能准时执行的原因,怎么解决?

  • 原因
    • 等待前面的任务执行:因为setTimeOut在事件循环机制中属于异步任务中的宏任务,会将回调函数放在由webApI提供的单独运行空间中等待相应的秒数以后,放在宏任务队列中等待执行。因此如果执行宏任务之前,需要执行耗费大量时间的同步任务和微任务,就会导致setTimeOut不能够准时执行。
    • 嵌套调用定时器5次以上存在最小时延4ms,系统设置
    • 未激活的页面,比如切换了标签页或者浏览器最小化,setTimeOut执行的最小执行间隔为1000ms
    • 执行时间有最大值,24.8天
  • 怎么解决?
    • 一般精度要求不高的业务场景使用setTimeOut问题不大
    • 如果使用js实现动画效果可以使用requestAnimationFrame(),执行间隔和浏览器刷新时长保持一致。

隐式转换规则

  • 运算符的隐式转换规则
    • 对于+运算符,如果两边含有字符串类型的,因为+运算符的特殊含义,因此会进行拼接字符串
            //有一边是字符串的时候,因为+的特殊含义,可以做字符串拼接
        console.log('2'+2)//22
        console.log('2'+'2')//22
        console.log('2'+true);//2true
    
    • 如果两边没有字符串类型,先使用Number做强制类型转换,再进行加减运算
            //如果没有字符串。就强制转换类型Number以后再进行计算
        console.log(2+true);//3
        console.log(Number(undefined))//NaN
        console.log(2+undefined);//NaN
        console.log(2+NaN);//NaN
        console.log(2+null);//2
    
    • -*/等其他运算符,都先使用Number强制转化为数字再运算
            //其余的运算符,都强制转化为Number类型以后再进行计算
        console.log('2'-2)//0
    
  • 比较运算符的转换规则
    • 如果不全是字符串类型的在比较大小,都需要先类型转换为Number,再比较大小。
            //如果不是全部是字符串类型的,都会转化为Number类型再进行比较
        console.log(2>'1');
        console.log(true>false)
        console.log('2'>true)
        console.log('2'>undefined)//false
        console.log(undefined>'2')//false
        console.log(2>null)//true
    
    • 如果全书字符串类型的比较大小,则一位一位比较unicode编码
            //如果两边都是字符串类型,就按照每一位的unicode编码比较
        console.log('abc'<'azd')//true
    
  • 关系运算符的转换规则
    • 关系运算符会将其他运算符转换为数字进行比较。
            //[]相当于转化为空字符串
        console.log([] == 0)//true
        console.log([] === 0)//false
    
    
        console.log(![] == 0)//true
    
        console.log([]==![])//true
    
        console.log({} == 0)//false
        console.log(Number({}))//NaN
    
        console.log({} == !{})//false
        console.log({}=={})//false
    
  • 布尔值转换为false的几个,如0、undefined、''、NaN、null、false
        //布尔值转换false的
          console.log(Boolean([]))//空数组转换为布尔值为false
        console.log(Boolean(0))
        console.log(Boolean(undefined))
        console.log(Boolean(''))
        console.log(Boolean(NaN))
        console.log(Boolean(null))
        console.log(Boolean(false))
  • 隐式转换的一些用法
    • 例如将数字转换为字符串:2+'';

箭头函数与普通函数的区别

  • this指向 箭头函数的this指向是静态的,永远指向函数声明时所在的作用域。普通函数的this指向函数调用时的作用域
  • 构造函数 由于this指向是静态的,所以箭头函数不能作为构造函数实例化对象。
  • call,apply,bind 由于this指向不能被改变,因此不能使用call,apply,bind
  • 实参 箭头函数不能使用arguments获取实参,可以用...rest获取
  • 匿名函数 箭头函数都是匿名函数
  • 书写方式 箭头函数可以省略(){}

js的数据类型

  • 基本数据类型:Number String Null Undefined Symbol Boolean
  • 引用数据类型:Object Array Function

typeof和instanceof区别

  • 可以判断的类型
    • typeof可以进行基本数据类型的判断(String、Boolean、Number、undefined)除了null。typeof null返回object。对于引用类型的判断,Array、Object都判断为Object。Function类型判断为function。
    • instanceof可以进行引用类型的判断,不能进行基本数据类型的判断。用于判断左边是否出现在右边的原型链上。左边沿着隐式原型链是否能找到右边的原型对象。
  • 用法与返回值不同
    • typeof XXX 返回xxx的数据类型
    • A instanceof B返回布尔值。
  • 一个准确判断数据类型的方法
    • 两者都有弊端,可以使用Object.prototype.toString.call(xxx).slice(8,-1).toLowerCase()
    • 封装一个判断数据类型的函数
function getType(obj){
  return Object.prototype.toString.call(obj).slice(8,-1).toLowerCase();
}

如何将类数组对象转化为数组

  • 如何判断是不是类数组 一个对象有length属性值,且key值是数字
  • 常见的类数组对象
    • arguments
    • document.getElementsByTagNamedocument.querySelectorAll 等等
  1. Array.from(arr);
  2. Array.prototype.slice.call(arr);
  3. [...arr]
  4. Array.prototype.concat.call([],arr)
  5. Array.prototype.map.call(arr, x => x)
  6. Array.prototype.filter.call(arr, x => 1)
  7. Array.apply(null,arr);

Set和Map的区别

  • 相同 都可以用来存储不重复的值,都有Iterator接口
  • 不同
    • Set是一种叫集合的数据结构,Map是叫字典的数据结构
    • 存储方式,Set是以[value,value]的方式存储数据,只有键值没有键名。Map是[key,value]的形式。
    • 方法:set.add(value) map.set(key,value)/set.has(value),map.has(key)/set.delete(value),map.delete(key)/set.clear(),map.clear()

WeakSet和WeakMap区别

  • WeakSet与Set
    • 添加的元素:WeakSet里面添加的元素只能为对象或者数组。
    • WeakSet里面的对象都是弱引用。即,垃圾回收机制会自动回收里面的对象,里面对象的引用不被垃圾回收机制考虑到,如果外界不再引用这个对象,但是WeakSet引用了,仍然会被垃圾回收机制回收掉。Set里面的对象是强引用,不会被回收
    • 不可遍历。由于垃圾回收机制随时可能回收WeakSet里面的对象,因此Weak Set不能被遍历,没有keys、entries、values...
  • WeakSet应用场景 可以用于存储Dom元素。(如果存储的Dom元素被移除,垃圾回收机制会自动回收,不会造成内存泄漏)
  • WeakMap与Map
    • 添加的元素。键名只能是弱引用对象,键值可以是任意的。
    • 因此键名是弱引用,键值为正常引用
    • 因此垃圾回收机制可能会随时消除其中的对象,不可以遍历,没有forEach()、keys/entries/values...
  • WeakMap的应用场景 用于存储Dom元素,DOM节点为key,附加信息为value

Map和Object的区别

  • key bject的key只能是字符串或者symbols,map的key可以是任意类型。
  • 顺序 bject添加键是无序的,数量需要手动计算。map是有序的,可以通过map.size计算
  • 原型 bject是有原型的,key可能会与原型上的属性名冲突。
  • Iterator bject没有Iterator属性,不能通过for-of遍历。Map有Iterator属性,可以迭代。

Iterator

  • Iterator 是一种接口,部署了Iterator接口的数据结构可以进行遍历操作。部署了Iterator接口的数据结构可以进行for-of操作。
  • 哪些数据结构部署了Iterator接口 Array、Map、Set、arguments、String、TypedArray、NodeList。
  • 实现原理
    • 创建一个指针对象,指向该结构的初始位置。
    • 对象中有next方法,调用next方法,指针指向数据结构的第一个成员
    • 不断调用next方法,直到指针指向最后一个成员
    • 每次调用next方法都返回一个包含value和done的对象。
  • 如何为一个Object对象部署Iterator接口。自定义Iterator
        let obj1 = {
            person:[
                '11',
                '22',
                '33',
                '44'
            ],
            //部署Iterator接口
            [Symbol.iterator](){
                let index = 0;
                let _this = this;
                return {
                    next(){
                        if(index<_this.person.length){
                            const result = {value:_this.person[index],done:false}
                            index++
                            return result
                        }else{
                            return {value:undefined,done:true}
                        }

                    }
                }
            }
        }
        for(v of obj1){
            console.log(v)
        }//11 22 33 44 

为一个对象部署Iterator接口

  • 前置知识
    • <Symbol.iterator{}>
    • Reflect.ownKeys(obj)可以将对象中的所有key值以数组的形式返回,并且包含symbol属性
    • Object.keys(obj)可以返回对象中所有keys,但是访问不到symbol属性。
//一个含有Symbol属性的对象
let person = {
name:'lxy',
age:18,
[Symbol('xx'):'xx'
}
  • 为一个对象部署Iterator接口
let obj = {
x:11,
y:22,
z:33,
//为其添加Symbol.iterator方法
[Symbol.iterator](){
let index = 0
let objArr = Reflect.ownKeys(this);
return{
    next(){
    //如果index没有遍历到objArr的结尾
    if(index<objArr.length-1){
        const res = {value:obj[objArr[index]],done:false}
        index++;
        return res;
    }
    return {value:undefined,done:true}
    }
}
}
}

有哪些迭代器方法

  • 不生成新数组的方法 forEach()、every()、some()、reduce()
  • 生成新数组的方法 map()、filter()

观察者模式与发布订阅模式的区别

观察者模式

  • 观察者是一对多的依赖关系,当被观察者的状态发生改变,会通知所有的观察者状态的变化。
  • 具体实现
    • 被观察者类中维护一个数组,用于在被观察者身上放置观察者

    • 定义一个方法,用于向观察者数组中添加观察者

    • 定义一个方法,用于向观察者数组中移除观察者

    • 定义一个方法,用于通知观察者数组中所有的观察者

    • 观察者类中,用于向某个被观察者身上添加观察者

    • 观察这类中设置收到消息的回调函数,用于判断是否收到被观察者的通知。

    //被观察者
        class Subject{
            constructor(){
                //定义一个[]用来在被观察者身上放置观察者
                this.observerList = [];
            }
            //定义一个方法,向被观察者身上添加observers
            addObserver(observer){
                this.observerList.push(observer)
            }
            //取消观察,观察者名单中移除相应的observer
            removeObserver(observer){
                //首先要找到这个观察者
                const index = this.observerList.findIndex(o=>o.name === observer.name);
                //从数组中移除
                this.observerList.splice(index,1)
            }
            //通知List中所有的观察者,执行notified方法,传入message参数
            notifyObervers(message){
                const observers = this.observerList;
                observers.forEach(observer =>{
                    return observer.notified(message)
                })
            }
        }
        class Observer{
            constructor(name,subject){
                //添加观察者的名字
                this.name = name;
                if(subject){
                    //给观察者指定被观察者。
                    subject.addObserver(this);
                }
            }
            notified(message){
                //收到通知以后,作出反应
                console.log(this.name,'got message',message)
            }
        }
        //定义一个被观察者
        let subject1 = new Subject();
        //为被观察者绑定两个观察者
        let observer1 = new Observer('observer1',subject1)
        let observer2 = new Observer('observer2',subject1);
        subject1.notifyObervers('aa');

发布订阅模式

  • 发布订阅模式是一种消息范式,发布者不会直接将消息通知给订阅者,而是将消息存放在消息队列中,由订阅者根据感兴趣的消息进行接收。
  • 实现思路
    • pubsub类中维护一个对象,用于放置发布者发布的事件和回调
    • $on 发布事件,向消息队列中添加事件和回调函数
    • $off 取消事件,从消息队列中移除相应的事件
    • $emit 订阅事件,从消息队列中执行相应的事件

观察者模式与发布订阅模式的区别

  • 发布订阅模式是一种消息范式,属于观察者模式
  • 观察者模式中,被观察者的变化会直接通知所有观察者,发布订阅模式,发布者不会直接将消息发布给订阅者,而是放在消息队列中。
  • 观察者大多数时间是同步的,发布订阅模式大多时候是异步的。

Es6与Common JS模块化

  • 模块化
    • 模块化可以防止命名冲突,提高代码复用性,可维护性
  • ES6模块化,使用export暴露,import引入
    • 分别暴露
    • 统一暴露
    • 默认暴露
  • CommonJS,使用module.exports和require
  • 区别
    • CommonJS是同步加载模块,只有加载完成,才能进行后面的操作。ES6为异步加载,不会造成堵塞浏览器,整个页面渲染完再执行模板脚本。
    • CommonJS模块输出一个值的拷贝,一旦输出一个值,模块内部变化将不能影响这个值。ES6输出值的引用,遇到import,生成一个引用,执行脚本时,根据这个引用去模块中取值。
    • CommonJS在运行时加载,加载一个对象,只有脚本运行时才能加载,且只加载一次。ES6模块编译时输出接口,在静态解析阶段生成。

DOM和BOM

DOM文档对象模型

  • DOM是指以对象形式描述HTML文档和XML文档,并且可以通过js对文档进行操作,获取标签,获取标签内容,修改标签属性等等操作。
  • 常见的dom类型:
    • document类型,代表整个页面文档,通过document的api可以选择页面元素和创建新的元素。
    • NodeList类型,使用qerySelectorAll获取
    • Element类型
  • dom操作:
    • 创建节点:createElement,createTextNode、createAttribute
    • 查询节点:querySelector、querySelectorAll
    • 更新节点:innerHTML、innerText、style
    • 添加节点:appendChild、insertBefore、setAttribute
    • 删除节点:removeChild

BOM浏览器对象模型

  • 浏览器提供的API,可以用于对浏览器窗口进行交互。核心对象是window。打开窗口,前进后退,弹窗等操作。
  • window的相关方法和属性
    • 弹窗,窗口属性。alert(),open(),prompt().innerWidth,innerHeight.
    • location属性用于url相关操作。href,reload()
    • history属性。back/forward/go前进和后退,pushState,replaceState()添加替换历史记录。
    • navigator属性,设备地理位置,是否启用cookie
    • screen,屏幕的宽高

js异步处理发展史

  • callBack最早处理异步是用回调函数的方式。不断嵌套回调函数,会导致回调地狱问题。
fs.readFile('路径',(err,data)=>{
    if(err)throw err
    fs.readFile('路径',(err,data2)=>{
        if(err)throw err
        fs.readFile('路径',(err,data3)=>{
            if(err)throw err
            处理数据
            ...
        }
    }
}
  • Promise是ES6的异步请求解决方案。用于解决回调函数产生的回调地狱问题。将嵌套调用变成链式调用。但是代码语义还是不够清晰,因此需要一种类似同步代码的写法达到异步的效果。
  • Generator是一个生成器函数,调用生成器函数并不会立刻执行,而是生成一个Iterator。通过调用next方法不断执行。调用一次next方法返回一个对象,包含value和done状态。
        function * getData(){
            let res = yield getJSON('Url')
            let res = yield getJSON('url2')
            let res = yield getJSON('url3');
        }
        
        //执行生成器函数
        let iterator = getData();
        iterator.next();
  • async/await是对generator做的封装,把执行生成器函数,并且不断调用next()方法并且传入上次返回值这一过程封装,自动执行。用async、await分别代替*和yield,拥有更好的语义。

    async await相比Generator方案做的改进

    1. 内置自动执行器,不需要手动执行生成器函数并不断调用next方法。
    2. 返回值为Promise,可以将返回值不为Promise的包装成Promise返回
    3. await后面可以是原始值或者Promise对象,而yiel后面只能是thunk函数或者Promise

Promise

  • 是什么?
    • 是ES6的异步请求解决方案。用于解决回调函数产生的回调地狱问题。将嵌套调用变成链式调用。
    • 是一个构造函数。传入一个函数作为参数,这个函数接受两个参数resolve和reject,resolve和reject分别是两个函数,resolve可以将Promise对象的状态变为成功,并且将异步操作的结果作为参数传递出去,reject使promise的状态变为失败,并且将异步操作报的错误作为参数传递出去。
  • Promise的状态
    • pending
    • fullfilled
    • rejected
  • 构造函数原型上的一些方法:
    • then方法:返回一个新的Promise,因此可以继续调用.then方法,链式调用的方式解决了回调地狱的问题。接收两个参数,分别是Promise成功和失败的回调。
    • catch方法:用来指定Promise失败的回调。并且可以为没有指定失败回调的then方法添加一个失败的回调函数,这样就可以一层一层的传递失败的参数,就是异常穿透。
  • 构造函数上的一些静态方法
    • Promise.resolve()返回一个Promise对象,如果是非Promise对象状态为成功,Promise对象取决于其状态。
    • Promise.reject()不管是什么都会返回一个失败的Promise对象。
    • Promise.all()接受一个Promise组成的数组,如果全部通过返回一个成功的Promise,返回的是由Promise值组成的新的Promise,如果有一个失败就返回失败的Promise对象。
    • Promise.race()接受一个Promise组成的数组,返回的Promise状态取决于最先改变状态的Promise
    • Promise.allSettled() 接收一个Promise组成的数组,等数组中所有Promise状态都变化了,返回所有Promise的值和状态组成的数组
    • Promise.any()接受一个Promise对象组成的数组,只要有一个Promise状态成功就返回第一个成功的实例,所有Promise都失败,就返回失败的Promise值为AggregateError。
  • Promise是先改变状态还是先指定回调?
    • 都有可能,若resolve是一个异步的,就会先指定回调,再改变状态
  • 异常穿透?
    • 只需要在.then后面加上.catch指定失败的回调,前面任意一个失败都会执行回调
  • 如何中断Promise链?
    • .then().then().then()...只有在上一个pending状态改变时会执行,因此如果其中有pending状态的Promise后面的将不会执行。

class怎么设置原型、静态、实例方法

class A{
    constructor(){} //设置原型
    实例方法(){}
    static 静态方法(){}
}

给DOM节点上添加事件的方法

  1. 直接在标签上添加<button onclick = "func(1)">按钮</button>
  2. 通过方法添加btn.onclick = function(){}
  3. 通过监听事件添加btn.addEventListener('click',func)

作用域与作用域链

  • 作用域
    • 变量的可访问性和可见性。在一定的空间内可以对数据进行读写操作,这个空间就是数据的作用域。
    • 分为全局作用域、函数作用域、块级作用域
  • 作用域链
    • 在js中使用一个变量的时候,会先在当前作用域下寻找该变量,如果没有找到,再不断向上层作用域中查找,直到全局作用域。这个不断向外层作用域寻找的过程就是作用域链。

作用域与执行上下文的区别

  • 作用域是静态的:在变量声明的时候就确定了。执行上下文是动态的,在调用函数执行的时候创建,在调用结束时释放。

原型与原型链

  • 什么是原型:
    • 每当定义一个函数数据类型(类)的时候,都会带一个prototype属性,指向函数的原型对象。
    • 通过函数实例化出来的__proto__属性指向原型对象。实例对象的__proto__属性指向构造函数的prototype属性。
    • 原型相当于一个公共区域,一个类的所有实例都可以访问到这个原型对象,作用是为每个实例对象存储公用的方法。
  • 什么是原型链:
    • 访问对象的某个属性,会先在对象本身的属性中查找,
    • 找不到就会通过__proto__属性向上查找,找它的构造函数的原型对象上的属性,
    • 再找不到就在依然沿着__proto__找到原型的原型寻找,直到找到Object对象的原型,Object对象的原型的原型指向null,如果没有在Object原型中找到就返回undefined。
    • 像这样一层一层的沿着隐式原型属性不断查找,形成的链式结构叫做原型链。

js中的继承

原型链继承

  • 子类构造函数的prototype原型对象指向父类的实例对象
  • 缺点
    • 不能向父类传参。会给子类所有实例对象的属性赋同一个值,影响了子类的所有实例对象。不符合面向对象的思想
    • 子类的实例对象共享了父类的引用属性,导致修改其中一个子类实例的属性,另一个实例的属性会被影响。
  • 具体实现
 //1/使用原型链实现继承
        function Person (age){
            this.name = 'haha';
            this.age = age;
        }//强调私有,不共享的
        Person.prototype.getName = function(){
            console.log(this.name)
        }//需要进行复用的方法
        function Student(school){
            this.school = school
        }
        Student.prototype = new Person;//定义子类型的原型等于父类型的实例对象
        //修正constructor,否则会指向Person
        Student.prototype.constructor = Student
        let student1 = new Student('meixueshang');
        let student2 = new Student('aaa');
        student1.getName();//子类型的实例对象可以访问到父类型的方法
        //原型中包含的方法被所有实例共享
        student2.getName()
        //子类在实例化的时候不能给父类构造函数传参

利用构造函数

  • 实现方法
    • 使用call关键字,拷贝父类实例的属性和方法。改变父类构造函数的上下文为当前类,并且传入参数为要继承的属性。
  • 特点
    • 优:解决了使用原型链继承的父类属性共享问题,每一个子类的实例可以有自己的属性。
    • 缺:但是父类中的方法无法实现复用,每次创建一个实例对象都会创建一遍方法,实例中的方法是不同的方法而不是复用的方法。
    • 缺:且子类实例不能继承父类构造函数原型上的方法。
  • 具体实现
        //利用构造函数实现继承,利用call关键字
        function Dog(name,color){
            this.name = name;
            this.color = color;
            this.say = function(){
                console.log('hello')
                }//不能实现方法复用,每次创建一次实例都是一个新的方法
        }
        Dog.prototype.getAll = function(){
            console.log(this.name,this.color)//获取不到原型上的方法
        }
        function littleDog(name,color,size){
            this.size = size;
            Dog.call(this,name,color);//将父类构造函数的上下文改变为子类
        }
        let littleDog1 = new littleDog('1','aa','small');
        console.log(littleDog1.name)
        //解决了父类共享问题,但是每创建一次子类,都会再创建一次方法

组合式继承

  • 实现方法
    • 先利用构造函数实现继承父类属性保留可以向父类传参的优点:在子类构造函数中使用call方法,改变父类的上下文。
    • 保留原型链继承的优点:将父类的方法放到原型链中,实现方法的复用,不用每次构建实例都去创建一遍方法。
  • 特点
    • 优:既能够实现每一个实例都能有自己的方法,还可以将方法定义在父类的原型对象中实现复用。
    • 缺:需要两次调用父类构造函数,会存在一份多余的父类实例属性。一次是<Xn.prototype = new Fn()>,一次是执行call方法又拷贝了一份父类的实例属性。
    • 缺:如果在子类构造函数的原型上添加方法,那么父类实例上也能够访问到这个方法。-----使用浅拷贝解决。
  • 代码实现
        //组合继承,把方法放在原型链上复用,并且每个子类可以有自己的属性
        function Animal(name,color){
            this.name = name;
            this.color = color;
        }
        Animal.prototype.getName = function(){
            return this.name;//原型方法通过原型链继承实现复用
        }
        function Cat(name,color,size){
            //使用构造函数实现继承父类的属性,可以每个子类有自己的属性,不共享父类中的属性数据
            Animal.call(this,name,color);
            this.size = size;
        }
        //同时将构造函数方法放在原型链上,实现方法的复用,不用每创建一次实例都创建一次方法
        Cat.prototype = new Animal();//但是这个语句会调用两次的父类构造函数
        //改一下constructor的指向。不改的话constructor会指向Animal
        Cat.prototype.constructor = Cat;

组合继承的优化

  • 具体优化:
    • 使用构造函数继承属性
    • Child.prototype = Parent.prototype,不用重新创建一遍父类的属性和方法,而是直接复用父类原型对象上的方法。
  • 特点
    • 解决了组合继承中创建了多余的一份父类实例属性的缺点。
    • 但是constructor的指向混乱,Child和Parent的constructor将指向同一个对象

寄生式组合继承

  • 实现方法
    • 不再直接将父类的prototype赋值给子类的prototype,而是通过Object.create生成了一个新的对象,将父类prototype作为原型生成一个新的对象赋值给子类prototype,这样父类子类的prototype就是互相独立的,constructor不会互相影响了。<Cat.prototype = Object.create(Animal.prototype);>
    • 新创建一个指向父类的原型对象。这样就可以在子类实例化的时候只执行一次父类的构造函数。
  • 特点
    • 具有组合式继承的优点
    • 且解决了组合式继承中调用了两次父类构造函数的缺点,只需要在子类实例化的时候调用一次父类构造函数。
  • 代码实现
        function Animal(name,color){
            this.name = name;
            this.color = color;
        }
        Animal.prototype.getName = function(){
            return this.name;//需要原型链继承实现复用
        }
        function Cat(name,color,size){
            //使用构造函数实现继承,可以每个子类有自己的属性,不共享父类中的属性数据
            Animal.call(this,name,color);
            this.size = size;
        }
        
        //实现寄生式组合继承
        //创建了一个新的对象,原型指向父类原型对象的新的对象,而不是将父类原型赋值给子类原型。
        //相互独立
        Cat.prototype = Object.create(Animal.prototype);
        //改一下constructor的指向
        Cat.prototype.constructor = Cat;

利用class实现继承

  • 实现方法
    • 利用extends和super关键字实现继承
  • 代码实现
        //利用class实现继承
        class School{
            constructor(name,price){
                this.name = name;
                this.price = price;

            }
            call(){
                //一个等价于School.prototype.call = function(){

                // }
                //使用时在实例对象上使用

            }
            static calling(){
                //一个等价于School.calling= function(){

                // }在父类上使用方法

            }

        }
        class Two extends School{
            constructor(name,price,level){
                super(name,price);
                this.level = level
            }

        }

0.1+0.2 !== 0.3

  • 结果
        console.log(0.1+0.2);//0.30000000000000004
        console.log(0.100000000000000002 === 0.1)//true
        console.log(0.200000000000000002 === 0.2)//true
  • 原因
    • 因为js的存储方式采用IEEE754双精度64位。先将数字转化为二进制进行运算,数字相加时以二进制的方式进行,呈现结果时才转换为十进制。
    • 当十进制的小数的二进制表示的有限数字超过52位,在js中不能精确存储,存在舍入误差
    • 0.1和0.2转换为二进制以后是无限循环的,
    • 因此被分别四舍五入为0.100000000..2和0.200000000...2
    • 相加为0.30000000000...4
  • 总结:因为0.1,0.2的二进制时无限循环的小数,js中采取64位IEEE754标准,只能表示52位小数,因此表示循环小数时存在舍入误差,精度丢失。
  • 解决:计算前将值转换为整数再进行计算。或者对结果截取前面的位数再比较。

[1,2,3].map(parseInt)

  • 结果
['1','2','3'].map(item=>{
return parseInt(item)}//1,2,3
['1','2','3'].map(parseInt)//1,NaN,NaN
  • 为什么会出现这样的结果
    • map接收两个参数,一个是回调函数,一个是thisArg.回调函数如果没有指定参数,默认传入item,index,arr,即当前正在处理的参数,当前参数的index,当前处理的数组。
    • 因此传入parseInt()中的参数有item,index
    • parseInt('1',0) //按照十进制
    • parseInt('2',1) //第二个参数的范围2-36
    • parseInt('3',2)
  • parseInt(要处理的值,基数)
    • 第一个参数是要进行转换的数字
    • 第二个参数为基数,范围2-36.表示以什么机制进行解析

事件机制事件流,事件触发

  • 事件流
    • 包括事件捕获阶段 --- 目标阶段 --- 事件冒泡阶段
    • 事件捕获:点击某个元素,不会立即触发,而是先捕获目标元素,从顶部向下捕获,直到找到目标元素
    • 目标阶段:到达目标元素
    • 事件冒泡:事件从目标元素逐级向上,冒泡到最顶部的元素上
  • 事件捕获和冒泡的设置
    • DOM 0级事件都是在事件冒泡阶段对事件响应。
    • DOM 2级事件接受三个参数。第三个参数默认false,表示在冒泡阶段处理事件。true表示在捕获阶段处理事件。
    btn.addEventListener('事件',function(){},true/false)

添加事件的方法

  • DOM0:绑定回调函数。onclick(点击事件),onscroll(滚动事件),oninput(输入事件)
  • DOM2:通过addEventListener(‘事件名’,回调函数,true/false),有三个参数,第一个是事件名,第二个是触发事件的回调函数,第三个是在冒泡阶段还是捕获阶段触发事件。

事件代理

  • 什么是事件代理
    • 事件代理就是把一组或者一个事件委托到父元素或者更外层的元素执行。
    • 这样当事件相应到目标元素上,可以通过事件冒泡机制触发到外层元素绑定的事件上,由外层元素来执行事件。
  • 什么场景下使用事件代理
    • ul<lixn。ul中有许多li,每一个li都需要绑定事件,只需要在ul上绑定事件,不需要在每一个li中都绑定事件,减少了注册事件,节省内存。
    • 需要动态绑定事件。比如ul中新增li,需要再次给这个li绑定事件。如果使用事件代理,将事件代理给父元素ul,就不需要重复绑定事件,减少了重复工作。
  • 如何判断点击的是哪一个子DOM
    • 通过event事件对象获取event.target,即为发生事件的子元素。
  • e.target和e.currentTarget的区别
    • e.currentTarget指的是事件绑定的元素。
    • e.target是触发事件的元素。

Javascript本地缓存的方式有哪些?

cookie

  • cookie
    • 是网站为了标识用户身份在用户终端中存储的数据。
    • (存储在客户端的一小段数据4k左右)cookie中的内容(sessionId或者Token)会随着http请求一起发送到服务器。
  • 一般由服务器设置
    • 可以设置过期时间,Expires和Max-age。每次请求都会携带在header中。4k左右
  • cookie需要注意安全性。
    • 通过设置属性,value:用于保存用户登录状态,应该加密,不能用明文的用户标识。
    • <http:only>减少XSS攻击。指定cookie无法通过js脚本拿到,比如Documnet.cookie,XMLHttpRequest对象和RequestAPI都无法获取到cookie。这样可以防止cookie被脚本读取到,只有浏览器发出http请求的时候才会带上该cookie。
    • 设置Secure属性。浏览器只有在加密协议HTTPS下,才能将cookie发送到服务器。
    • <same-site>规定浏览器不能在跨域请求中携带cookie,减少CSRF攻击。

localStorage

  • 本地存储,属于持久化存储,浏览器页面关闭以后数据任然存在。5M左右。
  • 场景:例如用户登录后存储token到本地存储中,这样关闭页面以后还是登录状态。
  • API:
   localStorage.setItem('name',xx);
   localStorage.getItem('name',xxx);

sessionStorage

  • 会话存储。非持久化存储,关闭页面以后就会被清理。5M左右
  • 场景:希望刷新页面以后,用户登录以后的信息仍然存在。
  • API
    sessionStorage.setItem('name',xxx);
    sessionStorage.getItem('name',xxx);

indexDB

  • 用于客户端存储大量结构化数据。存储量没有上限
  • 所有操作都是异步的
  • 支持存储js对象

区别

  • 大小:cookie不超过4k,local和session大小大约是5M
  • 失效时间:cookie可以设置过期时间,过期时间之前一直有效,localStorage关闭网页以后任然存在,属于持久性存储数据,除非主动删除。sessionStorage在关闭会话以后失效。
  • cookie始终在http请求中携带,会自动发送给服务器,localStorage和sessionStorage不会把数据发送给服务器,仅存储在本地。
  • cookie需要注意安全性设置。value后面保存用户信息应当加密。http-only防止XSS攻击。<same-site>防止XSRF攻击。

登录鉴权:Session,SessionID,Token,JWT

SessionID+cookie方式

  • 流程
    • 浏览器登录发送账号密码,服务器查询用户数据库,校验用户
    • 服务器把用户的登陆状态存为Session(内容包括签名,name,max-age等),并且生成一个SessionID
    • 通过登录接口返回,把SessionID设置到Cookie中(set cookie)
    • 浏览器再次发送请求时,SessionId就会伴随着Cookie,在请求头中带给服务器。
    • 服务器通过查验SessionID校验Session。
    • 校验了的登录状态以后就可以正常处理业务返回请求结果。
  • session方式特点:后端通过cookie存储sessionID,后端存储数据。
    • 1)但是session数量随着登陆用户的增多而增多,存储会增加很多导致服务器压力过大。
    • 2)如果服务器是一个集群,为了同步登陆状态,需要将sessionID同步到每一个机器上,增加了服务器的维护成本。
    • 3)存在cookie中会有一些安全问题。

Token方式

  • token?令牌:是通过服务器生成的一串字符串。用于校验用户身份,通常是保存在cookie或者local-Storage中,在请求头中带给服务器完成身份校验。
  • 流程
    • 用户登录,服务器校验账号密码
    • 把用户信息编码生成token,通过cookie.set把token放到浏览器中。(或者服务端将token返回给客户端,由客户端存储在local-Storage。
    • 当客户端再次发送请求时,token存储在cookie中在http请求中携带给服务器。(存储在local-storage中要从本地存储中读取,然后携带在http请求头中携带给服务器)
  • token方式特点:
    • 不需要后端存储,不会对服务器造成压力。
    • token可以存放在前端的任何地方,可以不保存在cookie中,提升了页面的安全性。
    • token下发以后,生效时间内一直有效,服务器无法收回token权限。
  • token生成方式(JWT)用于加密token信息。
    • header:指定了JWT的签名算法
    • playload:表明token的意图
    • signature:JWT的签名,防止被修改

SSO单点登录:

  • 公司内部搭建一个公共的认证中心,公司下所有的产品登录都在认证中心完成。一个产品在认证中心登陆后,再去访问另一个产品可以不用再次登录,即可以获取登陆状态。比如百度(文库,贴吧,地图...)

OAuth第三方登录

跨域与解决跨域

跨域

  • 是指不同域之间请求资源。协议、域名、端口号有任何一个不同就属于不同域。由于浏览器的同源策略,浏览器不能执行其他网站的脚本。跨域就是通过各种方式避开浏览器的同源策略向不同域请求资源。

解决方案

  • jsonp,利用script标签中的src属性不受同源策略的限制,可以访问跨域的js脚本。因此可以利用这一特点,服务端不再返回json格式的数据,而是返回一段调用某一个函数的js代码,在src中调用这个函数,得到数据,实现跨域。
    • 实现:创建script标签,src属性设置接口地址,接口参数中拼接一个自定义函数名,通过这个函数接收返回的数据。
    • 缺点:只支持get请求,jsonp需要后端配合返回指定格式的数据,有一定的安全隐患。
  • CROS(跨域资源共享):服务器在响应头中设置Acces-Control-Allow-Origin,浏览器将会允许跨域请求。
  • proxy实现代理:使用和浏览器同源的代理服务器转发前端发送的请求,由于服务器和服务器没有同源限制,所以可以通过代理服务器实现跨域。

闭包

  • 什么是闭包
    • 能够访问到其他函数作用域中的对象的函数,称为闭包。
    • 函数形成嵌套,并且内部的函数能够访问到外部函数作用域中的变量。那么这个包含了引用变量的函数称为闭包。
  • 实现原理
    • 利用了作用域链的特性,当前执行环境下访问某个变量时,如果不存在就会一直向外层寻找。
    • 为什么变量的生命周期延长了?上级作用域变量的生命周期,因为被下级作用域内引用,而没有被释放,就导致上级作用域内的变量,等到下级作用域执行完以后才正常得到释放。
  • 闭包产生的条件
    • 函数嵌套
    • 内部函数引用了外部函数的变量
  • 闭包的使用场景
    • 回调函数
    • 私有属性,私有方法(单例模式)
    • 防抖节流
    • 函数内部局部变量自增,计数器

js预编译

函数提升与变量提升

  • 函数提升 function声明的函数,在声明之前就可以调用
  • 变量提升 var声明的变量、函数,在定义前就可以访问到,值为undefined
  • 为什么函数提升优于变量提升?
    • 因为预编译的过程中,先将声明的变量作为AO对象的属性,赋值undefined,再将function声明的函数添加为方法,此时如果函数的名称和变量名称相同,就会覆盖声明的变量。

预编译

  • 预编译阶段都做那些事情?
    • 创建了js变量对象(AO)
    • 找到形参和变量的声明,作为AO对象的属性名,值为undefined
    • 实参和形参统一
    • 找函数声明。如果变量的声明和函数声明的名称一样,会覆盖变量的声明这也是函数提升优于变量提升的原因。
    //预编译:
    function fn(a,c){
        console.log(a) //function a(){}
        var a = 123
        console.log(a) //123
        console.log(c); //function c(){}
        function a(){}
        if(false){
            var d = 678
        }
        console.log(d);//undefined
        console.log(b);//undefined
        var b = function(){}
        console.log(b);//function (){}
        function c(){};
        console.log(c);//function c(){}
    }
    fn(1,2);

    //先创建一个AO对象,
    //找到里面声明的变量,变为AO对象的属性,然后赋值为undefined
    //将形参赋值为实参的值
    //找到声明的函数,如果和变量名冲突会覆盖
    // AO:{
    //     a:undefined 1 function a(){}
    //     c:undefined 2  function c(){}
    //     d:undefined 
    //     b:undefined    
    // }

js中的严格模式

  • 严格模式 use strict指令,出现在脚本或者函数的开头。表明在严格的条件下运行。
  • 严格模式的目的
    • 消除js语法中一些不合理不严谨的地方,减少一些怪异行为
    • 消除代码中一些不安全的地方,保证代码运行的安全
    • 提高编译效率
  • 严格模式做了哪些限制?
    • 不允许在声明变量之前就使用这个变量
    • 不允许使用delete x这种方式删除变量或者对象,或者delete fun这种方式删除函数
    • 函数参数不允许重名
    • 不允许使用八进制
    • 不允许使用with
    • 禁止this关键字指向全局对象
    • 不能对只读属性赋值
    • 变量名不能使用eval和arguments字符串

函数式编程

  • 主要的编程范式:命令式编程,声明式编程,函数式编程。
  • 函数式编程:
    • 函数式编程更加强调程序执行的结果而非执行的过程,把逻辑写成函数,定义好输入参数只关心输出结果。
    • 优点:可以更好地管理,实现代码复用,具有更好的组合性

图片格式,如何解决浏览器对图片的兼容?

  • 有哪些图片格式
    • png,jpeg,gif,svg,webp -webp格式图片加载速度最快,图片压缩体积只有jpeg的2/3,能够节省大量的服务器带宽和数据空间。质量相同的情况下webp格式的图片体积更小
  • 如何兼容webp
    • 使用js判断加载,对浏览器判断是否支持webp。先加载一张base64的webp格式,如果支持就加载,不支持就把图片后缀替换成jpg格式
    • 通过picture标签。source标签中为webp格式,img标签是jpg格式。这样如果加载不到webp就会加载jpg格式的图片。
    • 在请求头中携带image或者webp,表明当前浏览器支持什么格式的图片,服务器根据请求头返回不同格式的图片。

Doctype的作用?严格模式与混杂模式

  • Doctype
    • Doctype是文档类型(document type)的简写形式,是对html文档的文档类型声明。
    • !DOCTYPE 用于告知浏览器用何种模式渲染文档。不同的渲染模式会影响浏览器对css和js的解析。
    • 混杂模式也叫做怪异模式,按照自己的解析方式解析和渲染页面,不同的浏览器显示不同的样式。
    • 严格模式,开启标准模式,按照W3c的标准解析和渲染页面,页面在所有浏览器显示的都是同一个样式。
  • html中严格模式和混杂模式的区别之处
    • 盒模型;w3c标准的盒模型和ie盒模型的区别。严格模式统一使用w3c盒模型,内容不包括padding和border。但是混杂模式在ie或者低版本的浏览器中盒子内容还包括padding和border
    • 行内元素的宽高。严格模式下不能给行内元素设置宽高,但是混杂模式下会生效
    • 百分比高度。严格模式下如果父元素没有设置高度,而子元素的高度是百分比的形式,这个时候是不生效的。

HTML标签语义化的理解?

  • html标签语义化是什么?
    • 使用正确的标签表明页面的结构,看到标签就知道标签的内容。h5新增的一些语义化标签:header(头部),nav(导航),main(主体内容),h1-h6(标题),aside(侧边栏),footer(页脚)。
  • 为什么要使用标签语义化?
    • 在没有CSS样式的情况下,页面整体也会呈现很好的结构效果。
    • 代码可读性更强,结构更加清晰,有利于团队的开发和维护。
    • 有利于搜索引擎建立索引,有利于SEO,爬虫爬取更多信息,搜索引擎会根据不同的标签来赋予不同的权重,比如h标签中匹配到的关键词比较重要。
    • 有利于不同设备的解析,屏幕阅读器等,可以根据语义标签来渲染网页。
    • 提升用户体验,比图titile,alt用于解释名词和图片信息。

HTML5新特性有哪些?

  • 语义化标签
  • CSS新增animation,transition,transform
  • 新增了本地存储和会话存储,给浏览器增加了存储功能
  • 画布canvas
  • input表单元素增强:日期,搜索框,数值类...

操作DOM节点

  • 创建节点:createElement、createTextNode、innerHTML
  • 插入节点:appendChild、insertBefore、replaceChild、
  • 删除节点:removeChild、removeNode、innerHTML设为空