• js相关


    一、Object.defineProperty()方法

    vue的双向数据绑定就是通过Object.defineProperty()方法实现的,俗称属性拦截器。

    方法会直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。

    // 语法:
    /*
     * @param: obj:需要定义属性的对象; 
     *         prop:需要定义或修改的属性;
     *         descriptor:将被定义或修改属性的描述符
    */
    Object.defineProperty(obj,prop,descriptor)

    实例:

    var a = {};
    Object.defineProperty(a, 'b', {
        set: function(newValue) {
            console.log('赋值操作, 赋值' + newValue);
        },
        get: function() {
            console.log('取值操作');
            return 2;
        }
    });
    a.b = 1;    // 赋值操作,赋值1
    a.b;       // 取值操作2

     下面我们来写个实例看看,这里我们以Vue为参照去实现怎么写MVVM

    // index.html
    <body>
        <div id="app">
            <h1>{{song}}</h1>
            <p>《{{album.name}}》是{{singer}}2005年11月发行的专辑</p>
            <p>主打歌为{{album.theme}}</p>
            <p>作词人为{{singer}}等人。</p>
            为你弹奏肖邦的{{album.theme}}
        </div>
        <!--实现的mvvm-->
        <script src="mvvm.js"></script>
        <script>
            // 写法和Vue一样
            let mvvm = new Mvvm({
                el: '#app',
                data: {     // Object.defineProperty(obj, 'song', '发如雪');
                    song: '发如雪',
                    album: {
                        name: '十一月的萧邦',
                        theme: '夜曲'
                    },
                    singer: '周杰伦'
                }
            });
        </script>
    </body>

    上面是html里的写法,相信用过Vue的同学并不陌生

    那么现在就开始实现一个自己的MVVM吧

    打造MVVM

    // 创建一个Mvvm构造函数
    // 这里用es6方法将options赋一个初始值,防止没传,等同于options || {}
    function Mvvm(options = {}) {   
        // vm.$options Vue上是将所有属性挂载到上面
        // 所以我们也同样实现,将所有属性挂载到了$options
        this.$options = options;
        // this._data 这里也和Vue一样
        let data = this._data = this.$options.data;
    
        // 数据劫持
        observe(data);
    }

    数据劫持

    为什么要做数据劫持?

    • 观察对象,给对象增加Object.defineProperty

    • vue特点是不能新增不存在的属性 不存在的属性没有get和set

    • 深度响应 因为每次赋予一个新对象时会给这个新对象增加defineProperty(数据劫持)

    多说无益,一起看代码

    // 创建一个Observe构造函数
    // 写数据劫持的主要逻辑
    function Observe(data) {
        // 所谓数据劫持就是给对象增加get,set
        // 先遍历一遍对象再说
        for (let key in data) {     // 把data属性通过defineProperty的方式定义属性
            let val = data[key];
            observe(val);   // 递归继续向下找,实现深度的数据劫持
            Object.defineProperty(data, key, {
                configurable: true,
                get() {
                    return val;
                },
                set(newVal) {   // 更改值的时候
                    if (val === newVal) {   // 设置的值和以前值一样就不理它
                        return;
                    }
                    val = newVal;   // 如果以后再获取值(get)的时候,将刚才设置的值再返回去
                    observe(newVal);    // 当设置为新值后,也需要把新值再去定义成属性
                }
            });
        }
    }
    
    // 外面再写一个函数
    // 不用每次调用都写个new
    // 也方便递归调用
    function observe(data) {
        // 如果不是对象的话就直接return掉
        // 防止递归溢出
        if (!data || typeof data !== 'object') return;
        return new Observe(data);
    }


    以上代码就实现了数据劫持

    二、巧用JSON.stringify()生成漂亮格式的JSON字符串

    就是通过 JSON.stringify() 函数的第三个参数来指定缩进的空格数:
    // 此处为了示例, 采用字面量的形式构造了一个对象
    // 实际使用中, 一般是某个POJO,或者VO之类的值对象
    var myObject =  {
            "myProp": "myValue",
            "subObj": {
                "prop": "value"
            }
        };
    // 格式化
    var formattedStr = JSON.stringify(myObject, null, 2);
    生成的字符串如下所示:
    {
      "myProp": "myValue",
      "subObj": {
        "prop": "value"
      }
    }

    三、实现一个 sleep 函数

    比如 sleep(1000) 意味着等待1000毫秒,可从 Promise、Generator、Async/Await 等角度实现。

    1.

    //Promise
    const sleepp = time => {
      return new Promise(resolve => setTimeout(resolve,time))
    }
    sleepp(1000).then(()=>{
      console.log(1)
    })

    2.

    //Generator
    function* sleepGenerator(time) {
      yield new Promise(function(resolve,reject){
        setTimeout(resolve,time);
      })
    }
    sleepGenerator(1000).next().value.then(()=>{console.log(1)})

    3.

    //async
    function sleep(time) {
      return new Promise(resolve => setTimeout(resolve,time))
    }
    async function output() {
      let out = await sleep(1000);
      console.log(1);
      return out;
    }
    output();

    4.

    //ES5
    function sleep(callback,time) {
      if(typeof callback === 'function')
        setTimeout(callback,time)
    }
    
    function output(){
      console.log(1);
    }
    sleep(output,1000);

    四、数组去重并获取重复元素次数

        const AA = [1,2,3,4,2,1,2,2,4,6,4]
    
        function filter (arr) {
          var newArr = []
          var obj={}
          for(var i = 0; i < arr.length; i++){
            if(obj[arr[i]]){
              obj[arr[i]]++
            }else{
              newArr.push(arr[i])
              obj[arr[i]]=1
            }
          }
          var res = []
          for (var n in obj) {
            if(obj[n] > 1) {
              res.push(`重复元素:${n},重复了${obj[n]}次`)
            }
          }
          return res
        }
        console.log(filter(AA))

    五、合并数组并去重

      // 数组合并并去重
        let arr1 = [1, 1, 2, 3, 't', 9, 5, 5, 4]
        let arr2 = [1, 2, 5, 4, 9, 7, 7, 't', 8]
    function uniqueArr(arr1,arr2) { //合并两个数组 arr1.push(...arr2)//或者arr1 = [...arr1,...arr2] //去重 let arr3 = Array.from(new Set(arr1))//let arr3 = [...new Set(arr1)] return arr3 } console.log(uniqueArr(arr1, arr2))

    六、js实现汉字中文排序的方法 例如:省市列表的排序

    1.数组内的元素是中文字符串的简单排序

    var arr = ['南京', '北京', '上海', '杭州', '深圳'];
      function sortChinese (arr) { // 参数: 排序的数组
        arr.sort(function (item1, item2) {
          return item1.localeCompare(item2, 'zh-CN');
        })
      }
      sortChinese(arr)
      console.log(arr); //  ["北京", "杭州", "南京", "上海", "深圳"]

    2.数组内的元素是对象,以对象某一个属性进行排序

    var arr = [
        {name: '南京', code: '09', info: {province: '江苏'}},
        {name: '北京', code: '01', info: {province: '北京'}},
        {name: '上海', code: '02', info: {province: '上海'}},
        {name: '深圳', code: '05', info: {province: '广东'}}
      ];
      function sortChinese (arr, dataLeven) { // 参数:arr 排序的数组; dataLeven 数组内的需要比较的元素属性
        /* 获取数组元素内需要比较的值 */
        function getValue (option) { // 参数: option 数组元素
          if (!dataLeven) return option
          var data = option
          dataLeven.split('.').filter(function (item) {
            data = data[item]
          })
          return data + ''
        }
        return arr.sort(function (item1, item2) {
          return getValue(item1).localeCompare(getValue(item2), 'zh-CN');
        })
      }
      console.log(sortChinese(arr, 'name') // 例如:比较的是name,传入的就是 'name'
    ); /*[{name: '北京', code: '01', info: {province: '北京'}}, {name: '南京', code: '09', info: {province: '江苏'}}, {name: '上海', code: '02', info: {province: '上海'}}, {name: '深圳', code: '05', info: {province: '广东'}}]*/ sortChinese(arr, 'info.province') // 例如:比较的是数组元素属性info内的province属性,传入的就是 'info.province'  console.log(arr); /* [{name: '北京', code: '01', info: {province: '北京'}}, {name: '深圳', code: '05', info: {province: '广东'}}, {name: '南京', code: '09', info: {province: '江苏'}}, {name: '上海', code: '02', info: {province: '上海'}}]*/

    3.对国内的所有省份进行排序,并且首字母相同的第一个添加首字母

    function chineseLetter (arr, dataLeven) {
        var letter = 'abcdefghjklmnopqrstwxyz'.split('')
        var zh = "阿八嚓哒妸发旮哈讥咔垃痳拏噢妑七呥扨它穵夕丫帀".split('')
        /* 获取数组元素比较的值 */
        function getValue (option) {
          if (!dataLeven) return option
          var data = option
          dataLeven.split('.').filter(function (item) {
            data = data[item]
          })
          return data + ''
        }
        /* 进行排序 */
        arr.sort(function (item1, item2) {
          return getValue(item1).localeCompare(getValue(item2), 'zh-Hans-CN')
        })
        /* 判断需要排序的字符串是否含有中文字符 */
        if (/[u4e00-u9fff]/.test(getValue(arr[0])) && typeof arr[0] === 'object') pySegSort(0, 0)
        /* 给省列表中添加首字符 */
        function pySegSort (letterIndex, zhIndex) {
          var first = true // 首次是否加 字母标识
          for (var i = zhIndex; i < arr.length; i++) {
            var item = arr[i]
            //      是否有值 && 当前值大于等于本次字母的最小值 && (最后一位 || 当前值小于下次字母的最小值)
            var state = zh[letterIndex] && getValue(item).localeCompare(zh[letterIndex], 'zh') >= 0 && (letterIndex === letter.length - 1 || getValue(item).localeCompare(zh[letterIndex+1], 'zh') < 0)
            if (state) { // 满足条件,同一个首字母下的:例如 A 下的所有省份
              if (first) { //是否是第一次出现
                item.letter = letter[letterIndex].toUpperCase()
                first = false
              } else {
                item.letter = ''
              }
            } else { // 递归调用 函数,进行下次字母下的排列
              letterIndex++
              if (letterIndex < letter.length) {
                pySegSort(letterIndex, i)
                break
              }
            }
          }
        }
      }
      chineseLetter(provinceList, 'value')
      console.log(provinceList)

    七、js事件循环、宏任务和微任务

    事件循环中的同步任务,异步任务:

    • 同步和异步任务在不同的执行"场所",同步的进入主线程,异步的进入Event Table执行并注册函数。

    • 当指定的异步事情完成时,Event Table会将这个函数移入Event Queue

    • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,推入主线程执行。

    • js引擎的monitoring process进程会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。上述过程会不断重复,也就是常说的Event Loop(事件循环)。

    console.log('先执行这里');
    setTimeout(() => {
        console.log('执行啦')
    },3000);//先执行这里// ... 3s later  3秒到了,计时事件timeout完成,定时器回调进入Event Queue,主线程执行已执行完,此时执行定时器回调。
    // 执行啦
    setTimeout(() => {
        task()
    },3000)
    
    sleep(10000000)

    上述代码在控制台执行task()需要的时间远远超过3秒,执行过程:

    • task()进入Event Table并注册,计时开始。

    • 执行sleep函数,很慢,非常慢,计时仍在继续。

    • 3秒到了,计时事件timeout完成,task()进入Event Queue,但是sleep也太慢了吧,还没执行完,只好等着。

    • sleep终于执行完了,task()终于从Event Queue进入了主线程执行。

    上述的流程走完,setTimeout这个函数是经过指定时间后,把要执行的任务(本例中为task())加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于3秒。

    重点来了:定时器的毫秒,不是指过ms秒执行一次fn,而是过ms秒,会有fn进入Event Queue。

    setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。

    关于setTimeout要补充的是,即便主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒

    对于执行顺序来说,setInterval会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了。

    宏任务和微任务

    除了广义的同步任务和异步任务,我们对任务有更精细的定义:

    • macro-task(宏任务):包括整体代码script,setTimeout,setInterval

    • micro-task(微任务):Promise,process.nextTick

    不同类型的任务会进入对应的Event Queue,比如setTimeout和setInterval会进入相同的Event Queue。

    在宏任务和微任务概念中的事件循环机制:

    主任务(宏任务)完——所有微任务——宏任务(找到宏任务其中一个任务队列执行,其中如果又有微任务,该任务队列执行完就执行微任务)——宏任务中另外一个任务队列(里面偶微任务就再执行微任务)。

    总的来说就是在宏任务和微任务之间来回切。下面列子执行过程:

    第一轮:主线程输出:【1,7】,添加宏任务【set1,set2】,添加微任务【6,8】。执行完主线程,然后执行微任务输出【6,8】

    第二轮:执行宏任务其中一个任务队列set1:输出【2,4】,执行任务的过程,碰到有微任务,所以在微任务队列添加输出【3,5】的微任务,在set1宏任务执行完就执行该微任务,第二轮总输出:【2,4,3,5】

    第三轮:执行任务另一个任务队列set2:输出【9,11】,执行任务的过程,碰到有微任任务,所以在微任务队列添加输出【10,12】的微任务,在set2宏任务执行完就执行该微任务,第三轮总输出:【9,11,10,12】

    整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。(请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)

    console.log('1'); //第一轮主线程【1】
     
    setTimeout(function() { //碰到set异步,丢入宏任务队列【set1】:我将它命名为set1
         console.log('2');//第二轮宏任务执行,输出【2】
         process.nextTick(function() {//第二轮宏任务执行,碰到process,丢入微任务队列,【3】
             console.log('3');
         })
         new Promise(function(resolve) {//第二轮宏任务执行,输出【2,4】
             console.log('4');
            resolve();
        }).then(function() {
            console.log('5')//第二轮宏任务执行,碰到then丢入微任务队列,【3,5】
        })
    })
    process.nextTick(function() { //碰到process,丢入微任务队列【6】
        console.log('6'); //第一轮微任务执行
    })
    new Promise(function(resolve) { 
        console.log('7'); //new的同时执行代码,第一轮主线程此时输出【1,7】
        resolve();
    }).then(function() {
        console.log('8') //第一轮主线程中promise的then丢入微任务队列,此时微任务队列为【6,8】。当第一轮微任务执行,顺序输出【6,8】
    })
    
    setTimeout(function() { //碰到set异步丢入宏任务队列,此时宏任务队列【set1.set2】:我将它命名为set2
        console.log('9');//第三轮宏任务执行,输出【9】
        process.nextTick(function() { //第三轮宏中执行过程中添加到微任务【10】
            console.log('10');
        })
        new Promise(function(resolve) {
            console.log('11');//第三轮宏任务执行,宏任务累计输出【9,11】
            resolve();
        }).then(function() {
            console.log('12') //第三轮宏中执行过程中添加到微任务【10,12】
        })
    })

    八、防抖与节流

    防抖指触发事件后在n秒内函数只执行一次,若在n秒内再次触发则重新计算

    节流指连续发生的事件在n秒内只执行一次函数

    防抖和节流通常用于优化即时查询

    九、正则

    1.匹配url地址

    let strUrl = 'https://www.bilibili.com/video/BV1n4411m7TQ/?p=7'
    let regUrl = /^(?:(http|https|ftp)://)?((?:[w-]+.)+[a-z0-9]+)((?:/[^/?#]*)+)?(?[^#]+)?(#.+)?$/i;
    console.log(regUrl.exec(strUrl))

    十、数据类型检测

    在JS中有这几种方法来判断数据类型:typeof、instanceof、Object.prototype.toString.call()、constructor

    1.typeof

    Array,Object,null,Date,RegExp,Error这几个类型都被 typeof 判断为 object,所以如果想要判断这几种类型,就不能使用 typeof 了。

    Number,String,Boolean,Function,undefined,如果想判断这几种类型,那就可以使用 typeof。

    var o = new Array();
    console.log(typeof 1);//number
    console.log(typeof 'xiaoming');//string
    console.log(typeof true);//boolean
    console.log(typeof undefined);//undefined
    console.log(typeof null);//object
    console.log(typeof o);//object
    console.log(typeof new RegExp());//object

    2. instanceof

    object instanceof constructor -> object表示某个实例对象,contructor表示某个构造函数,用来检测 constructor.prototype 是否存在于参数 object 的原型链上(判断对象是不是另一个对象实例),如:

    function Foo(){}
    var foo = new Foo()
    console.log(foo instanceof Foo); // true
    console.log([] instanceof Array);//true
    console.log({} instanceof Object);//true
    console.log(function(){} instanceof Function);//true

    instanceof也可以判断简单数据类型,但是简单数据类型不能以字面量的形式创建,否则返回值为false;如果通过new关键字去创建简单数据类型,则返回值为true; instanceof不能区别undefined和null。

    console.log(1 instanceof Number);//false
    console.log('xiaoming' instanceof String);//false
    console.log(true instanceof Boolean);//false
    console.log('--------------------------------');
    console.log(new Number(1) instanceof Number);//true
    console.log(new String('xiaoming') instanceof String);//true
    console.log(new Boolean(true) instanceof Boolean);//true
    console.log(
        123 instanceof Number, //false
        'dsfsf' instanceof String, //false
        false instanceof Boolean, //false
        [1,2,3] instanceof Array, //true
        {a:1,b:2,c:3} instanceof Object, //true
        function(){console.log('aaa');} instanceof Function, //true
        undefined instanceof Object, //false
        null instanceof Object, //false
        new Date() instanceof Date, //true
        /^[a-zA-Z]{5,20}$/ instanceof RegExp, //true
        new Error() instanceof Error //true
    )

    3. Object.prototype.toString.call()

    Object.prototype.toString.call()能够准确的判断所有的数据类型;

    alert(Object.prototype.toString.call('') === ‘[object String]’) -------> true;
    alert(Object.prototype.toString.call(2) === ‘[object Number]’) -------> true;
    alert(Object.prototype.toString.call([]) === ‘[object Array]’) -------> true;
    alert(Object.prototype.toString.call(new Date()) === ‘[object Date]’) -------> true;
    alert(Object.prototype.toString.call(new RegExp()) === ‘[object RegExp]’) -------> true;
    alert(Object.prototype.toString.call(null) === ‘[object Null]’) -------> true;
    alert(Object.prototype.toString.call(e) === ‘[object Function]’) -------> true;
    alert(Object.prototype.toString.call(f)
    === ‘[object Function]’) -------> true; 大小写不能写错,比较麻烦,但胜在通用。

    4. constructor

    undefined和null没有contructor属性

    var bool = true
    var num = 1
    var str = 'abc'
    var und = undefined
    var nul = null
    var arr = [1,2,3]
    var obj = {name:'haoxl',age:18}
    var fun = function(){console.log('I am a function')}

    function Person(){}
    var per = new Person()
    function Student(){}
    Student.prototype = new Person()
    var haoxl = new Student()
    console.log(bool.constructor === Boolean);// true
    console.log(num.constructor === Number);// true
    console.log(str.constructor === String);// true
    console.log(arr.constructor === Array);// true
    console.log(arr.constructor === Object);// false
    console.log(obj.constructor === Object);// true console.log(fun.constructor === Function);// true console.log(haoxl.constructor === Student);// false console.log(haoxl.constructor === Person);// true

    constructor不能判断undefined和null,并且使用它是不安全的,因为contructor的指向是可以改变的

    alert(c.constructor === Array) ----------> true
    alert(d.constructor === Date) -----------> true
    alert(e.constructor === Function) -------> true
    注意: constructor 在类继承时会出错
    eg:
          function A(){};
          function B(){};
          A.prototype = new B(); //A继承自B
          var aObj = new A();
          alert(aobj.constructor === B) -----------> true;
          alert(aobj.constructor === A) -----------> false;
    而instanceof方法不会出现该问题,对象直接继承和间接继承的都会报true:
          alert(aobj instanceof B) ----------------> true;
          alert(aobj instanceof B) ----------------> true;
    言归正传,解决construtor的问题通常是让对象的constructor手动指向自己:
          aobj.constructor = A; //将自己的类赋值给对象的constructor属性
          alert(aobj.constructor === A) -----------> true;
          alert(aobj.constructor === B) -----------> false; //基类不会报true了;

    5. 无敌万能的方法:jQuery.type()

    如果对象是undefined或null,则返回相应的“undefined”或“null”。
    jQuery.type( undefined ) === "undefined"
    jQuery.type() === "undefined"
    jQuery.type( window.notDefined ) === "undefined"
    jQuery.type( null ) === "null"
    如果对象有一个内部的[[Class]]和一个浏览器的内置对象的 [[Class]] 相同,我们返回相应的 [[Class]] 名字。 (有关此技术的更多细节。 )
    jQuery.type( true ) === "boolean"
    jQuery.type( 3 ) === "number"
    jQuery.type( "test" ) === "string"
    jQuery.type( function(){} ) === "function"
    jQuery.type( [] ) === "array"
    jQuery.type( new Date() ) === "date"
    jQuery.type( new Error() ) === "error" // as of jQuery 1.9
    jQuery.type( /test/ ) === "regexp"
    其他一切都将返回它的类型“object”。

    6. Array.isArray() 

    可以判断是否是数组类型

    Array.isArray([1, 2, 3]);  // true
    Array.isArray({foo: 123}); // false
    Array.isArray('foobar');   // false
    Array.isArray(undefined);  // false

    十一、浏览器页面渲染机制

    1) 浏览器会解析三个东西:

    • 一是HTML/SVG/XHTML,HTML字符串描述了一个页面的结构,浏览器会把HTML结构字符串解析转换DOM树形结构。
    • 二是CSS,解析CSS会产生CSS规则树,它和DOM结构比较像。
    • 三是Javascript脚本,等到Javascript 脚本文件加载后, 通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree。

    2)解析完成后,浏览器引擎会通过DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree。

    • Rendering Tree 渲染树并不等同于DOM树,渲染树只会包括需要显示的节点和这些节点的样式信息。
    • CSS 的 Rule Tree主要是为了完成匹配并把CSS Rule附加到Rendering Tree上的每个Element(也就是每个Frame)。
    • 然后,计算每个Frame 的位置,这又叫layout和reflow过程。

    3)最后通过调用操作系统Native GUI的API绘制

    这里重要要说两个概念,一个是Reflow,另一个是Repaint

    重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。
    回流:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)
    我们知道,当网页生成的时候,至少会渲染一次。在用户访问的过程中,还会不断重新渲染。重新渲染会重复回流+重绘或者只有重绘。回流必定会发生重绘,重绘不一定会引发回流。重绘和回流会在我们设置节点样式时频繁出现,同时也会很大程度上影响性能。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。

    如何减少回流、重绘

    • 使用 transform 替代 top
    • 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)
    • 不要把节点的属性值放在一个循环里当成循环里的变量。

        for(let i = 0; i < 1000; i++) {

          // 获取 offsetTop 会导致回流,因为需要去获取正确的值

          console.log(document.querySelector('.test').style.offsetTop)

        }

    • 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
    • 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
    • CSS 选择符从右往左匹配查找,避免节点层级过多
    • 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于 video 标签来说,浏览器会自动将该节点变为图层。

    性能优化策略
    基于上面介绍的浏览器渲染原理,DOM 和 CSSOM 结构构建顺序,初始化可以对页面渲染做些优化,提升页面性能。

    JS优化: <script> 标签加上 defer属性 和 async属性用于在不阻塞页面文档解析的前提下,控制脚本的下载和执行。 defer属性: 用于开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。 async属性: HTML5新增属性,用于异步下载脚本文件,下载完毕立即解释执行代码。
    CSS优化: <link> 标签的 rel属性中的属性值设置为preload能够让你在你的HTML页面中可以指明哪些资源是在页面加载完成后即刻需要的,最优的配置加载顺序,提高渲染性能
    总结


    综上所述,我们得出这样的结论:

    浏览器工作流程:构建DOM -> 构建CSSOM -> 构建渲染树 -> 布局 -> 绘制。
    CSSOM会阻塞渲染,只有当CSSOM构建完毕后才会进入下一个阶段构建渲染树。
    通常情况下DOM和CSSOM是并行构建的,但是当浏览器遇到一个不带defer或async属性的script标签时,DOM构建将暂停,如果此时又恰巧浏览器尚未完成CSSOM的下载和构建,由于JavaScript可以修改CSSOM,所以需要等CSSOM构建完毕后再执行JS,最后才重新DOM构建。

    ————————————————
    版权声明:本文为CSDN博主「十年呵护」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/zzhuan_1/article/details/90199891

    十二、实现深克隆

    1 递归

    function deepClone(obj) {
            // 过滤特殊情况
            if(obj === null) {
                return null;
            }
            if(typeof obj !== "object") {
                return obj;
            }
            if(obj instanceof RegExp) {
                return new RegExp(obj);
            }
            if(obj instanceof Date) {
                return new Date(obj);
            }
    
            // 不直接创建空对象,目的是克隆的结果和之前保持相同的所属类
            let newObj = new obj.constructor;
            for(let key in obj) {
                if(obj.hasOwnProperty(key)) {
                    newObj[key] = deepClone(obj[key]);
                }
            }
        }

    2 JSON.parse(JSON.stringfy(obj))

    十三、原型 继承 prototype __proto__

    每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法

    对象__proto__属性的值就是它所对应的原型对象:

    var one = {x: 1};
    var two = new Object();
    one.__proto__ === Object.prototype // true
    two.__proto__ === Object.prototype // true
    one.toString === one.__proto__.toString // true

    prototype

    首先来说说prototype属性,不像每个对象都有__proto__属性来标识自己所继承的原型,只有函数才有prototype属性。

    为什么只有函数才有prototype属性?ES规范就这么定的。

    开玩笑了,其实函数在JS中真的很特殊,是所谓的_一等公民_。JS不像其它面向对象的语言,它没有类(class,ES6引进了这个关键字,但更多是语法糖)的概念。JS通过函数来模拟类。

    当你创建函数时,JS会为这个函数自动添加prototype属性,值是空对象 值是一个有 constructor 属性的对象,不是空对象。而一旦你把这个函数当作构造函数(constructor)调用(即通过new关键字调用),那么JS就会帮你创建该构造函数的实例,实例继承构造函数prototype的所有属性和方法(实例通过设置自己的__proto__指向承构造函数的prototype来实现这种继承)。

    小结

    虽然对不熟悉的人来说还有点绕,但JS正是通过__proto__prototype的合作实现了原型链,以及对象的继承。

    构造函数,通过prototype来存储要共享的属性和方法,也可以设置prototype指向现存的对象来继承该对象。

    对象的__proto__指向自己构造函数的prototypeobj.__proto__.__proto__...的原型链由此产生,包括我们的操作符instanceof正是通过探测obj.__proto__.__proto__... === Constructor.prototype来验证obj是否是Constructor的实例。

    回到开头的代码,two = new Object()Object是构造函数,所以two.__proto__就是Object.prototype。至于one,ES规范定义对象字面量的原型就是Object.prototype

    更深一步的探讨

    我们知道JS是单继承的,Object.prototype是原型链的顶端,所有对象从它继承了包括toString等等方法和属性。

    Object本身是构造函数,继承了Function.prototype;Function也是对象,继承了Object.prototype。这里就有一个_鸡和蛋_的问题:

    Object instanceof Function // true
    Function instanceof Object // true

    原文链接:https://github.com/creeperyang/blog/issues/9
  • 相关阅读:
    JavaScript原型详解
    jQuery插件开发全解析
    再谈JavaScript闭包及应用
    狗日的Javascript中的闭包
    UML的基本图(一)
    项目开发-树形层级结构中的数量统计
    不要再坑人啦!NSOperation才是苹果推荐使用的多线程技术!
    linux下查看文件内容cat,more,less
    cocos2d-x -- 渠道SDK【棱镜】接入(2)
    关于虚拟机装kali-linux的联网问题
  • 原文地址:https://www.cnblogs.com/lingnweb/p/9983851.html
Copyright © 2020-2023  润新知