JS基础和ES6相关

1、检测数据类型的方法

(1)typeof----使用 typeof 方法来检测数据类型,基本类型大部分都能被准确检测并返回正确的字符串(除了 Null 类型,其返回 object 字符串),而引用类型大部分都不能够被准确检测(除了 Function 类型能够准确返回 function 字符串外,其它的都返回了 object 字符串)
(2)object.prototype.tostring()-----Object.prototype.toString 返回的 [object,class] 字符串中,class 准确的表示了各个数据的类型,与 typeof 不同的是,class 所代表的数据类型字符串首字母是大写的,而不像 typeof 返回的是小写字符串。

var toString=Object.prototype.toString;

console.log(toString.call(und));  // [object Undefined]
console.log(toString.call(nul));  // [object Null]
console.log(toString.call(boo));  // [object Boolean]
console.log(toString.call(num));  // [object Number]
console.log(toString.call(str));  // [object String]
console.log(toString.call(obj));  // [object Object]
console.log(toString.call(arr));  // [object Array]
console.log(toString.call(fun));  // [object Function]
console.log(toString.call(date));  // [object Date]
console.log(toString.call(reg));  // [object RegExp]
console.log(toString.call(err));  // [object Error]
console.log(toString.call(arg));  // [object Arguments]
2、执行上下文(执行上下文就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念)

(1)

执行上下文有三种类型
全局执行上下文:它做了两件事:1. 创建一个全局对象,在浏览器中这个全局对象就是 window 对象。2. 将 this 指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。
函数执行上下文:次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。
Eval函数执行上下文

(2)执行栈(先进后出)-----当 JavaScript 引擎首次读取你的脚本时,它会创建一个全局执行上下文并将其推入当前的执行栈。每当发生一个函数调用,引擎都会为该函数创建一个新的执行上下文并将其推到当前执行栈的顶端。
引擎会运行执行上下文在执行栈顶端的函数,当此函数运行完成后,其对应的执行上下文将会从执行栈中弹出,上下文控制权将移到当前执行栈的下一个执行上下文。
(3)执行上下文创建过程
《1》创建阶段
       (1)确定 this 的值,也被称为 This Binding。
在全局执行上下文中,this 的值指向全局对象,在浏览器中,this 的值指向 window 对象。
在函数执行上下文中,this 的值取决于函数的调用方式。如果它被一个对象引用调用,那么 this 的值被设置为该对象,否则 this 的值被设置为全局对象或 undefined(严格模式下)。
       (2)LexicalEnvironment(词法环境-----词法环境是一个包含标识符变量映射的结构) 组件被创建。

在词法环境中,有两个组成部分:(1)环境记录(environment record) (2)对外部环境的引用
环境记录是存储变量和函数声明的实际位置。
对外部环境的引用意味着它可以访问其外部词法环境。
词法环境有两种类型:
全局环境(在全局执行上下文中)是一个没有外部环境的词法环境。全局环境的外部环境引用为 null。它拥有一个全局对象(window 对象)及其关联的方法和属性(例如数组方法)以及任何用户自定义的全局变量,this 的值指向这个全局对象。
函数环境,用户在函数中定义的变量被存储在环境记录中。对外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境。

       (3)VariableEnvironment(变量环境) 组件被创建。
《2》执行阶段

3、闭包(使用场景、优缺点)

(1)概念
(2)使用场景

  • 实现对象的私有数据
  • 创建有状态的函数
  • 函数式编程中,闭包经常用于偏函数应用和柯里化

(3)优缺点

3、js异步
3、继承

八种方式、

  1. 原型链继承----------缺点是多个实例对引用类型的操作会被篡改
function SuperType() {
    this.property = true;
}

SuperType.prototype.getSuperValue = function() {
    return this.property;
}

function SubType() {
    this.subproperty = false;
}

// 这里是关键,创建SuperType的实例,并将该实例赋值给SubType.prototype
SubType.prototype = new SuperType(); 

SubType.prototype.getSubValue = function() {
    return this.subproperty;
}

var instance = new SubType();
console.log(instance.getSuperValue()); // true

在这里插入图片描述
2. 借用构造函数继承(使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型))

function  SuperType(){
    this.color=["red","green","blue"];
}
function  SubType(){
    //继承自SuperType
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color);//"red,green,blue,black"

var instance2 = new SubType();
alert(instance2.color);//"red,green,blue"

缺点—:只能继承父类的实例属性和方法,不能继承原型属性/方法/无法实现复用,每个子类都有父类实例函数的副本,影响性能
3. 组合继承(组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。)

  1. 原型式继承
  2. 寄生式继承
  3. 寄生组合式继承
  4. 混入方式继承多个对象
  5. ES6类继承extends
4、ES6 promise &generator&async&await(dianjizheli查看详细情节)

async 函数是 Generator 函数的语法糖。使用 关键字 async 来表示,在函数内部使用 await 来表示异步。想较于 Generator,Async 函数的改进在于下面四点:
内置执行器。Generator 函数的执行必须依靠执行器,而 Aysnc 函数自带执行器,调用方式跟普通函数的调用一样
更好的语义。async 和 await 相较于 * 和 yield 更加语义化
更广的适用性。co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise对象。而 async 函数的 await 命令后面则可以是 Promise 或者 原始类型的值(Number,string,boolean,但这时等同于同步操作)
返回值是 Promise。async 函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用

Generator 的方式解决了 Promise 的一些问题,流程更加直观、语义化。但是 Generator 的问题在于,函数的执行需要依靠执行器,每次都需要通过 g.next() 的方式去执行。
promise 的方式虽然解决了 callback hell,但是这种方式充满了 Promise的 then() 方法,如果处理流程复杂的话,整段代码将充满 then。语义化不明显,代码流程不能很好的表示执行流程。

async 函数完美的解决了上面两种方式的问题。流程清晰,直观、语义明显。操作异步流程就如同操作同步流程。同时 async 函数自带执行器,执行的时候无需手动加载。

(1)语法

  • async 函数返回一个 Promise 对象(如果 async 函数内部抛出异常,则会导致返回的 Promise 对象状态变为 reject 状态。抛出的错误而会被 catch 方法回调函数接收到。)
async function  f() {
    return 'hello world'
};
f().then( (v) => console.log(v)) // hello world


async function e(){
    throw new Error('error');
}
e().then(v => console.log(v))
.catch( e => console.log(e));
  • async 函数返回的 Promise 对象,必须等到内部所有的 await 命令的 Promise 对象执行完,才会发生状态改变(只有当 async 函数内部的异步操作都执行完,才会执行 then 方法的回调。)
const delay = timeout => new Promise(resolve=> setTimeout(resolve, timeout));
async function f(){
    await delay(1000);
    await delay(2000);
    await delay(3000);
    return 'done';
}

f().then(v => console.log(v)); // 等待6s后才输出 'done'
  • 正常情况下,await 命令后面跟着的是 Promise ,如果不是的话,也会被转换成一个 立即 resolve 的 Promise

(2)async的错误处理

  1. 当 async 函数中只要一个 await 出现 reject 状态,则后面的 await 都不会被执行。解决办法:可以添加 try/catch。如果有多个 await 则可以将其都放在 try/catch 中。

(3)如何在项目上使用
依然是通过 babel 来使用。
只需要设置 presets 为 stage-3 即可。
安装依赖:

npm install babel-preset-es2015 babel-preset-stage-3 babel-runtime babel-plugin-transform-runtime

修改.babelrc:

"presets": ["es2015", "stage-3"],
"plugins": ["transform-runtime"]

(4)解决回调地狱、

4、垃圾回收机制

在前面我们先了解一下,为什么使用不当的闭包会在IE(IE9)之前造成内存泄漏呢,因为之前的js引擎用的是垃圾回收算法是引用标记法,对于循环引用将会导致GC无法回收“应该被回收”的内存。造成了无意义的内存占用,也就是内存泄漏。什么是内存泄漏呢,就是程序动态分配的堆内存由于某种原因程序未释放或者无法释放,造成系统内存的浪费,导致程序运行的速度减慢甚至系统奔溃等严重后果。

GC----- 找到内存空间中的垃圾回收垃圾,让程序员能再次利用这部分空间。

(1)引用计数法

优势
可即刻回收垃圾,当被引用数值为0时,对象马上会把自己作为空闲空间连到空闲链表上,也就是说。在变成垃圾的时候就立刻被回收。
因为是即时回收,那么‘程序’不会暂停去单独使用很长一段时间的GC,那么最大暂停时间很短。
不用去遍历堆里面的所有活动对象和非活动对象
劣势-------占内存/内存泄漏
计数器需要占很大的位置,因为不能预估被引用的上限,打个比方,可能出现32位即2的32次方个对象同时引用一个对象,那么计数器就需要32位。
最大的劣势是无法解决循环引用无法回收的问题 这就是前文中IE9之前出现的问题

(2)标记清除法(标记阶段(把所有活动对象做标记)、清除阶段(把没有标记的(非活动对象)销毁))
标记:GC从全局作用域的变量,沿作用域逐层往里遍历(对,是深度遍历),当遍历到堆中对象时,说明该对象被引用着,则打上一个标记,继续递归遍历(因为肯定存在堆中对象引用另一个堆中对象),直到遍历到最后一个(最深的一层作用域)节点。
清除:又要遍历,这次是遍历整个堆,回收没有打上标记的对象。

优点
解决循环引用的问题,因为两个对象从全局对象出发无法获取。因此,他们无法被标记,他们将会被垃圾回收器回收。
实现简单,打标记也就是打或者不打两种可能,所以就一位二进制位就可以表示
缺点
造成碎片化(有点类似磁盘的碎片化)
再分配时遍次数多,如果一直没有找到合适的内存块大小,那么会遍历空闲链表(保存堆中所有空闲地址空间的地址形成的链表)一直遍历到尾端

(3)复制算法
将一个内存空间分为两部分,一部分是From空间,另一部分是To空间,将From空间里面的活动对象复制到To空间,然后释放掉整个From空间,然后此刻将From空间和To空间的身份互换,那么就完成了一次GC。

5、this、改变this指向的方法(call、apply、bind)、

(1)this

this 永远指向最后调用它的那个对象

(2)改变this指向的方法:

  • 使用箭头函数-------(箭头函数的 this 始终指向函数定义时的 this,而非执行时。箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”)
    var name = "windowsName";

    var a = {
        name : "Cherry",

        func1: function () {
            console.log(this.name)     
        },

        func2: function () {
            setTimeout( () => {
                this.func1()
            },100);
        }

    };

    a.func2()     // Cherry
  • 使用new实例化

  • 在函数内部使用_this = this-----(我们是先将调用这个函数的对象保存在变量 _this 中,然后在函数中都使用这个 _this,这样 _this 就不会改变了。)

  • ` var name = “windowsName”;

    var a = {

     name : "Cherry",
    
     func1: function () {
         console.log(this.name)     
     },
    
     func2: function () {
         var _this = this;
         setTimeout( function() {
             _this.func1()
         },100);
     }
    

    };

    a.func2() // Cherry`

  • 使用call、apply、bind

//apply
    var a = {
        name : "Cherry",

        func1: function () {
            console.log(this.name)
        },

        func2: function () {
            setTimeout(  function () {
                this.func1()
            }.apply(a),100);
        }

    };

    a.func2()            // Cherry
//call
    var a = {
        name : "Cherry",

        func1: function () {
            console.log(this.name)
        },

        func2: function () {
            setTimeout(  function () {
                this.func1()
            }.call(a),100);
        }

    };

    a.func2()            // Cherry
//bind

    var a = {
        name : "Cherry",

        func1: function () {
            console.log(this.name)
        },

        func2: function () {
            setTimeout(  function () {
                this.func1()
            }.bind(a)(),100);
        }

    };

    a.func2()            // Cherry

这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this 对象的值。首先,apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array 的实例,也可以是arguments 对象;
对于call()方法而言,第一个参数是this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来,
bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列,bind 是创建一个新的函数,我们必须要手动去调用:
事实上,传递参数并非apply()和call()真正的用武之地;它们真正强大的地方是能够扩充函数
赖以运行的作用域


//apply
     var a ={
        name : "Cherry",
        fn : function (a,b) {
            console.log( a + b)
        }
    }

    var b = a.fn;
    b.apply(a,[1,2])     // 3
//call
    var a ={
        name : "Cherry",
        fn : function (a,b) {
            console.log( a + b)
        }
    }
//bind
    var b = a.fn;
    b.call(a,1,2)       // 3
    var a ={
        name : "Cherry",
        fn : function (a,b) {
            console.log( a + b)
        }
    }

    var b = a.fn;
    b.bind(a,1,2)()           // 3

6、原型链与继承

(1)
prototype在函数出生时就定义好了,是个对象,是Person构造函数构造出对象的爹,就是祖先,通过该构造函数产生的对象,可以继承该原型的属性和方法,原型也是对象。
(2)应用

//Person.prototype ---->原型
    //Person.prototype ={}是构造函数构造出对象的公有祖先,即父亲与很多儿子的关系
   // Car.prototype.height = 1000000;
    //Car.prototype.width = 1122222;
    //Car.prototype.lang = "fffff"
    Car.prototype = {
    		height = 1000000;
        	width = 1122222;
        	lang = "fffff";
    }
    function Car(color,owner){
        this.color = color;
        this.owner = owner;
        // this.height = 1000000;
        // this.width = 1122222;
        // this.lang = "fffff";

    }

    var car = new Car('red','wuyar')

  • 提取公有部分放在原型里,提取公有属性

(3)原型的增删改查(通过对象无法修改,即子无法修改父亲)
都要通过Car.prototype操作
(4)constructor----------------返回构造对象的构造函数(原型内部自带的属性),可以手动更改
(5)proto ---------------返回原型__prototype__:Car.prototype,可以更改。寻找属性的时候,如果自己没有,那么通过__prototype__在Car.prototype上寻找该属性
(6``)


    Person.prototype.name = 'sunny'
    function Person(){

    }
    //Person.prototype.name = 'cherry'//访问person.name都是cherry,修改了属性,修改了房间里面的东西
    var person = new Person();
    //Person.prototype.name = 'cherry'//访问person.name都是cherry

    Person.prototype={
        name : 'cherry'
    }//这种访问person.name是sunny,把原型改了,换了个新对象,在new的时候,var this = {__prototype__:Person.prototype},后面再修改的话不会修改之前的指向,这是新开了一个房间,之前的还指向之前旧的房间

最后一个过程相当于

在这里插入图片描述
如果把修改的Person.prototype={ name : 'cherry' }放在 var person = new Person();之前的话,就在生成指向之前修改 了房间的内容,所以会是cherry
(7)原型链
例子在这里插入图片描述
(8)继承

7、数组去重详情点击
8、深拷贝与浅拷贝(主要区别就是其在内存中的存储类型不同,深复制和浅复制只针对像 Object, Array 这样的复杂对象的。简单来说,浅复制只复制一层对象的属性,而深复制则递归复制了所有层级。)

栈(stack)为自动分配的内存空间,它由系统自动释放;而堆(heap)则是动态分配的内存,大小不定也不会自动释放。
基本数据类型的特点:直接存储在栈(stack)中的数据,引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
基本数据类型存放在栈中,引用类型存放在堆中
基本数据类型值不可变,基本数据类型的值是不可变的,动态修改了基本数据类型的值,它的原始值也是不会改变的;引用类型值可变
基本类型的比较是值的比较;引用类型的比较是引用的比较
引用类型(object)是存放在堆内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况开进行特定的分配
传值-------进行赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,然后再把再将值赋值到新的栈中;基本类型的赋值的两个变量是两个独立相互不影响的变量。
传址-------引用类型的赋值是传址。只是改变指针的指向,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响

因为浅复制只会将对象的各个属性进行依次复制,并不会进行递归复制,而 JavaScript 存储对象都是存地址的,所以浅复制会导致 obj.arr 和 shallowObj.arr 指向同一块内存地址 ,深复制则不同,它不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深复制的方法递归复制到新对象上。这就不会存在上面 obj 和 shallowObj 的 arr 属性指向同一个对象的问题。

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

//深拷贝  使用map 可以处理地址,利用键值对,保存key,检查map里面是否有克隆过的对象,有的话直接返回,解决循环引用,没有话将当前对象作为key,克隆对象作为value进行存储在进行判断,继续克隆,最后返回,使用weakmap的原因是 可以被垃圾回收机制回收,如果是强引用的话必须要赋值为空才可以释放空间,当深拷贝的时候会用到很多的空间,会造成内存的浪费,所以用weakmap
function clone(targrt,map = new WeakMap){    
	if(typeof targrt == 'object'){       
 		let cloneobject =Array.isArray(targrt)?[]:{}       
  		if(map.get(targrt)){           
   			return map.get(targrt)        
   		}        
   		map.set(target,cloneobject)        
   		for(let  key in targrt){            
   			cloneobject[key] =clone(targrt[key],map);        
   		}        
   		return cloneobject;    }    
   	else{        
  		 return targrt;    }
}
//性能优化
//考虑把for in 循环改为while循环


function clone(target, map = new WeakMap()) {
    if (typeof target === 'object') {
        const isArray = Array.isArray(target);
        let cloneTarget = isArray ? [] : {};

        if (map.get(target)) {
            return map.get(target);
        }
        map.set(target, cloneTarget);

        const keys = isArray ? undefined : Object.keys(target);
        forEach(keys || target, (value, key) => {
            if (keys) {
                key = value;
            }
            cloneTarget[key] = clone2(target[key], map);
        });

        return cloneTarget;
    } else {
        return target;
    }
}
9、JS事件循环机制
10、函数节流&函数防抖(不希望在事件持续触发的过程中那么频繁地去执行函数。)

防抖--------防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

  • 非立即执行函数----------触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
function debounce(func, wait) {
    let timeout;
    return function () {
        let context = this;
        let args = arguments;

        if (timeout) clearTimeout(timeout);
        
        timeout = setTimeout(() => {
            func.apply(context, args)
        }, wait);
    }
}
  • 立即执行函数----------立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
function debounce(func,wait) {
    let timeout;
    return function () {
        let context = this;
        let args = arguments;

        if (timeout) clearTimeout(timeout);

        let callNow = !timeout;
        timeout = setTimeout(() => {
            timeout = null;
        }, wait)

        if (callNow) func.apply(context, args)
    }
}

节流(连续触发事件但是在 n 秒中只执行一次函数。 节流会稀释函数的执行频率。)

  • 时间戳版
function throttle(func, wait) {
    var previous = 0;
    return function() {
        let now = Date.now();
        let context = this;
        let args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}
  • 定时器版
function throttle(func, wait) {
    let timeout;
    return function() {
        let context = this;
        let args = arguments;
        if (!timeout) {
            timeout = setTimeout(() => {
                timeout = null;
                func.apply(context, args)
            }, wait)
        }

    }
}

时间戳版和定时器版的节流函数的区别就是,时间戳版的函数触发是在时间段内开始的时候,而定时器版的函数触发是在时间段内结束的时候。

合在一起双剑合璧的节流

//节流之双剑合璧版
function throttle(doSomething, wait) {
    var timeout, _this, _arguments,
        previous = 0;

    var later = function() {
        previous = +new Date();
        timeout = null;
        doSomething.apply(_this, _arguments)
    };
    var throttled = function() {
        var now = +new Date();
        //下次触发 doSomething 剩余的时间
        var remaining = wait - (now - previous),
            _this = this;
            _arguments = arguments;
         // 如果没有剩余的时间了
        if (remaining <= 0) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            doSomething.apply(_this, _arguments);
        } else if (!timeout) {
            timeout = setTimeout(later, remaining);
        }
    };
    return throttled;
}
//触发onmousemove事件
xcd.onmousemove = throttle(doSomething,1000);



//完整的防抖与节流实例
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>防抖</title>
</head>
<body>
  <button id="debounce">点我防抖!</button>

  <script>
    window.onload = function() {
      // 1、获取这个按钮,并绑定事件
      var myDebounce = document.getElementById("debounce");
      myDebounce.addEventListener("click", debounce(sayDebounce));
    }

    // 2、防抖功能函数,接受传参
    function debounce(fn) {
      // 4、创建一个标记用来存放定时器的返回值
      let timeout = null;
      return function() {
        // 5、每次当用户点击/输入的时候,把前一个定时器清除
        clearTimeout(timeout);
        // 6、然后创建一个新的 setTimeout,
        // 这样就能保证点击按钮后的 interval 间隔内
        // 如果用户还点击了的话,就不会执行 fn 函数
        timeout = setTimeout(() => {
          fn.call(this, arguments);
        }, 1000);
      };
    }

    // 3、需要进行防抖的事件处理
    function sayDebounce() {
      // ... 有些需要防抖的工作,在这里执行
      console.log("防抖成功!");
    }

  </script>
</body>
</html>


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>节流</title>
</head>
<body>

  <button id="throttle">点我节流!</button>

  <script>
    window.onload = function() {
      // 1、获取按钮,绑定点击事件
      var myThrottle = document.getElementById("throttle");
      myThrottle.addEventListener("click", throttle(sayThrottle));
    }

    // 2、节流函数体
    function throttle(fn) {
      // 4、通过闭包保存一个标记
      let canRun = true;
      return function() {
        // 5、在函数开头判断标志是否为 true,不为 true 则中断函数
        if(!canRun) {
          return;
        }
        // 6、将 canRun 设置为 false,防止执行之前再被执行
        canRun = false;
        // 7、定时器
        setTimeout( () => {
          fn.call(this, arguments);
          // 8、执行完事件(比如调用完接口)之后,重新将这个标志设置为 true
          canRun = true;
        }, 1000);
      };
    }

    // 3、需要节流的事件
    function sayThrottle() {
      console.log("节流成功!");
    }

  </script>
</body>
</html>


(1)节流在工作中的应用:

  1. 懒加载要监听计算滚动条的位置,使用节流按一定时间的频率获取。
  2. 用户点击提交按钮,假设我们知道接口大致的返回时间的情况下,我们使用节流,只允许一定时间内点击一次。
  • 在说浏览器页面之前,我们需要了解两个点,浏览器解析与重绘与回流
10、作用域链([[scope]]保存作用域链,保存了执行上下文的集合,仅供js引擎存取)
    function a(){
        function b(){
            var bb = 256;
           // aa = 0;
        }
        var aa = 124;
        b();
        //console.log(aa); // 0
    }
    var glob = 100;
    a();

    //a定义时  a.[[scope]] ----->    0:GO{}
    //a执行时, a.[[scope]] ------>  0:AO{}(当a()执行结束后,销毁自己的上下文,如果里面包含一个函数的话,函数也就没了,回到定义状态等待下次执行)
                    //              1:GO{}
    //b定义时,拿的是a的劳动成果    b.[[scope]] ------>  0:AO{}
                                       //              1:GO{}
    //b执行时,生成自己的作用域     b.[[scope]] ------>  0:bAO{}(b的执行上下文,函数执行结束之后销毁自己的上下文,回到定义状态等待下次执行)
    //                                                 1:aAO{}(a的执行上下文)这里的和a的是一样的,并不是新生成的,假设函数b里面设置aa=0,那么在b()执行结束后,aa就等于0
    //                                                 2:GO{}
    //找变量的时候,我们需要再执行时的执行上下文中找,并不是定义时的上下文,并且产生的复用的上一个函数的执行上下文都是一个,并不是新产生的,只是借用的。
//a如果不执行的话,b是不可能被定义的

下一个例题
在这里插入图片描述
在这里插入图片描述
如果c()再次执行的话,会产生新的执行上下文,但是bAO、aAO、GO都不变,只产生新的cAO

11、重绘与回流

(1)重绘-------当元素样式不影响布局时,浏览器将使用重绘对元素进行更新,损耗较少,常见的操作有:改变元素颜色、改变元素背景色
(2)回流-------重排,当元素的尺寸、结构或者触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。常见的有页面的初次渲染,浏览器窗口大小改变、添加或者删除可见的 DOM 元素、元素字体大小变化
(3)避免大量使用重绘与回流的方法

  • 避免频繁操作样式,可汇总后统一一次修改
  • 尽量使用 class 进行样式修改,而不是直接操作样式
  • 减少 DOM 的操作,可使用字符串一次性插入
12、浏览器解析URL

1.用户输入 URL 地址。
2.对 URL 地址进行 DNS 域名解析。
3.建立 TCP 连接(三次握手)。
4.浏览器发起 HTTP 请求报文。
5.服务器返回 HTTP 响应报文。
6.关闭 TCP 连接(四次挥手)。
7.浏览器解析文档资源并渲染页面。
在这里插入图片描述
在这里插入图片描述

13、DNS域名解析

(1)DNS-------------域名系统 的英文缩写,提供的服务是用于将主机名和域名转换为 IP 地址的工作
(2)过程

  • 浏览器根据地址,在自身缓存中查找 DNS(域名服务器) 中的解析记录。如果存在,则直接返回 IP 地址;如果不存在,则查找操作系统中的 hosts 文件是否有该域名的 DNS 解析记录,如果有就返回。
  • 在条件 1 中的浏览器缓存或者操作系统的 hosts 文件中都没有这个域名的 DNS 解析记录,或者已经过期,则向域名服务器发起请求解析这个域名。
  • 先向本地域名服务器中请求,让它解析这个域名,如果解析不了,则向根域名服务器请求解析。
  • 根服务器给本地域名服务器返回一个主域名服务器。
  • 本地域名服务器向主域名服务器发起解析请求。
  • 主域名服务器接收到解析请求后,查找并返回域名对应的域名服务器的地址。
  • 域名服务器会查询存储的域名和 IP 的映射关系表,返回目标 IP 记录以及一个 TTL(Time To Live)值。
  • 本地域名服务器接收到 IP 和 TTL 值,进行缓存,缓存的时间由 TTL 值控制。
  • 将解析的结果返回给用户,用户根据 TTL 值缓存在本地系统缓存中,域名解析过程结束。
    在这里插入图片描述
15、三次握手与四次挥手

(1)三次握手
在这里插入图片描述
(2)四次挥手
在这里插入图片描述

16、浏览器渲染过程
  1. 浏览器通过 HTMLParser 根据深度遍历的原则把 HTML 解析成 DOM Tree。
  2. 浏览器通过 CSSParser 将 CSS 解析成 CSS Rule Tree(CSSOM Tree)。
  3. 浏览器将 JavaScript 通过 DOM API 或者 CSSOM API 将 JS 代码解析并应用到布局中,按要求呈现响应的结果。
  4. 根据 DOM 树和 CSSOM 树来构造 render Tree。
  5. layout:重排(也可以叫回流),当 render tree 中任一节点的几何尺寸发生改变,render tree 就会重新布局,重新来计算所有节点在屏幕的位置。
  6. repaint:重绘,当 render tree 中任一元素样式属性(几何尺寸没改变)发生改变时,render tree 都会重新画,比如字体颜色,背景等变化。
  7. paint:遍历 render tree,并调动硬件图形 API 来绘制每个节点。
    在这里插入图片描述
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值