【读书笔记】《JS高级程序设计》

102 阅读9分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 5 天,点击查看活动详情

  1. 正则字面量方法和正则对象,对象对于特殊字符要双重转义

    let reg="\[bc\]at"   //字面量
    let reg=new RegExp("\\[bc\\]at", "i");   //对象
    
  2. callee:该属性是一个指针,指向拥有这个 arguments 对象的函数(可用于递归调用)

    function sum() {
          console.log(arguments.callee);   //指向sum函数
    }
    sum()   
    
  3. caller:这个属性中保存着调用当前函数的函数的引用

    let obj = {
          sum() {
            console.log(obj.sum.caller);  //报错
        }
    }
    obj.sum()
    
  4. 每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象(创建时机)

  5. 所有基本包装类型的对象都会被转换为布尔值 true

     let bool = new Boolean(false)
     console.log(bool == false)    //true
     console.log(bool && true)     //true
    
  6. 数据属性和访问器属性 的区分方式,看是否有value属性

    var book = {
      _year: 2004
    };
    //这样的操作会报错,把访问器属性的get使用在数据属性上面
    Object.defineProperty(book, "_year", {
      get: function (key) {
        return book["_year"];
      }
    });
    
  7. 实例中的原型指针仅指向原型对象,而不指向构造函数,所以当原型对象被另外一个对象覆盖,原来的实例将无法找到最新的原型对象

  8. 函数的作用域链被保存在内部的 [[Scope]] 属性中(因为函数的作用域在函数声明就已经确定好了)

  9. JavaScript 将 function 关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号

    +function (){
    
    }()
    
  10. 使用 var 语句添加的 window 属性有一个名为 [[Configurable]] 的特性,这个特性的值被设置为 false ,因此这样定义的属性不可以通过 delete 操作符删除

    var age = 10
    window.name = "李白"
    delete age  //无法删除
    delete name
    console.log(age)   //10
    console.log(name)  //报错
    
  11. 尝试访问未声明的变量会抛出错误,但是通过查询 window 对象,可以知道某个可能未声明的变量是否存在

    var age=w_age  //报错
    var age=window.w_age   //undefined
    
  12. with可以把局部变量变成类似windows一样

    let obj={
        user:"李白",
        info:{
            
        }
    }
    with(document){
        with(obj){
        	with(obj.info){
            	console.log(user)
            }
        }
    }
    
    • with 可以将一个没有或有多个属性的对象处理为一个完全隔离的词法作用域,因此这个对象的属性也会被处理为定义在这个作用域中的词法标识符
    • 尽管 with 块可以将一个对象处理为词法作用域,但是这个块内部正常的 var声明并不会被限制在这个块的作用域中,而是被添加到 with 所处的函数作 用域中。
    • 作用:
      • 把对象的属性变成这个作用域的变量,改变变量也会造成对象发生改变
      • 在里面声明未出现的变量,就会声明到with的作用域内部
      • 如果LHS未找到,那就在顶级作用域window添加变量
  13. 节点类型

    • JavaScript 中的所有节点类型都继承自 Node 类型

      节点类型描述
      DocumentJavaScript 通过 Document 类型表示文档
      Element所有的节点,div、span
      Text纯文本内容
      Comment注释节点
      CDATASectionXML的CDATA 区域
      DocumentType包含着与文档的 doctype 有关的所有信息
      DocumentFragment文档碎片
      Attr元素的特性和属性

14.动态脚本

  • 动态JS

    function loadScriptString(code){
        let script=document.createElement("script")
        script.type = "text/javascript";
        script.text=code
        document.body.appendChild(script)
    }
    
  • 动态CSS

    function loadStyleString(css){
        var style = document.createElement("style");
        style.type = "text/css";
        style.appendChild(document.createTextNode(css));
        document.body.appendChild(style)
    }
    
  1. onLoad事件

    • image上面,只要指定了src,就会进行加载,实现图片的预加载
    • scriptstyle上面必要指定资源,并且要挂载到dom上面,才会加载
  2. unload 事件实在页面所有元素完全卸载了触发,那么在页面加载后存在的那些对象,此时就不一定存在了。此时,操作 DOM节点或者元素的样式就会导致错误。

  3. focus blur事件不会触发事件冒泡,focusoutfocusin

  4. form.submit() 提交表单不会触发表单的submit事件,form.reset()重置表单会触发表单的reset事件

  5. buttonsubmitclick事件触发是不固定的(不同浏览器不一样,最好处理提交逻辑在submit事件中)

  6. 在默认情况下,只有表单字段可以获得焦点。对于其他元素而言,如果先将其tabIndex 属性设置为-1,然后再调用 focus() 方法,也可以让这些元素获得焦点

  7. change 事件在不同表单控件中触发的次数会有所不同。对于 <input><textarea> 元素,当它们从获得焦点到失去焦点value 值改变时, 才会触发 change 事件。

  8. keypress事件是键盘输入事件,可以通过阻止他的默认事件阻止键盘输入。inputkeydown等事件都不能阻止输入。

  9. 只要代码中包含 finally 子句,那么无论 try 还是 catch 语句块中的 return 语句都将被忽略

  10. JS常见的报错类型

    错误类型描述
    RangeError数值超出相应范围时触发:Array(-20)、Array( Number.MAX_VALUE)
    ReferenceError访问不存在的变量时
    TypeError类型访问错误:var o = new 10;
  11. 继承的几种方式

    1. 原型链继承

      function Father(){
          this.colors = ["red", "blue"]
      }
      function Son(name){
          this.name = name
      }
      Son.prototype = new Father()
      
      • 缺点
        • 如果原型中包含引用类型的数据(Array、OBject),在一个对象中会影响其他整体的原型对象
        • 在创建原型关系时,不能向原型对象传参
    2. 借用构造函数继承

      function Father(name){
          this.name = name
          this.level ="父级"
      }
      function Son(){
          Father.call(this,name)
          this.level="子级"
      }
      
      • 缺点
        • 无法使用父级原型上面的方法
    3. 组合继承

      function Father(name){
          this.name = name
          this.level ="父级"
      }
      function Son(){
          Father.call(this,name)
          this.level="子级"
      }
      Son.prototype = new Father()
      
      • 缺点
        • 父级的构造函数需要调用两次,生成两次属性
    4. 原型式继承

      function Father(name){
          this.name = name
          this.level ="父级"
      }
      let Fn = function(){}
      Fn.prototype = new Father()
      let son = new Fn()
      
      • 缺点
        • 无法继承非原型上面的方法
        • 无法自定义原型属性
    5. 寄生继承

      function Father(name){
          this.name = name
          this.level ="父级"
      }
      function Son(){
          this.level="子级"
      }
      let Fn = function(){}
      Fn.prototype = new Father()
      let son = new Fn()
      son.toSay = function(){
          console.log("toSay")
      }
      Son.prototype = son
      

      缺点

      • 无法继承非原型上面的方法
    6. 寄生组合式继承

      function Father(name){
          this.name = name
          this.level ="父级"
      }
      function Son(){
          Father.call(this,name)
          this.level="子级"
      }
      let Fn = function(){}
      Fn.prototype = new Father()
      let son = new Fn()
      son.toSay = function(){
          console.log("toSay")
      }
      Son.prototype = son
      

      ES6就是使用的寄生组合式继承实现

  12. 普通对象的constructor属性指向他的构造函数,原型对象的constructor属性也指向构造函数

    • 实例对象.__proto__.constructor = 创建自己的构造函数=实例对象.constructor

  13. JS原型规则

    • 函数即对象
    • Function函数同时是自己的构造函数
    • Function函数同样是Object这类内置对象的构造函数
    • Function.prototype===Function.__proto__

  14. Object.create(null)创建一个纯净的空对象

  15. Object.assign()的原理

    Object.assign = function (target, varArgs) {
        var to = Object(target);  //这样会创建一个和target一样的对象
        for (var index = 1; index < arguments.length; index++) {
            var nextSource = arguments[index];
            if (nextSource != null) {
                for (var nextKey in nextSource) {
                    if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                        to[nextKey] = nextSource[nextKey];
                    }
                }
            }
        }
        return to;
    };
    
    • 传递一个参数,就等于对象赋值
  16. 拷贝

    • 浅拷贝

      只拷贝第一层属性,如果属性是复杂类型,那么只会复制地址

      • Object.assign()

        let obj1={name:1}
        //注意:Object.assign要为一个空对象
        let obj2=Object.assign({},obj1)
        
      • 展开运算符 ...

        let obj1 = { name: 'Kobe', address:{x:100,y:100}}
        let obj2= {... obj1}  //创建一个新对象
        
      • Array.prototype.concat()

        let arr = [1, 3, {username: 'kobe'}];
        let arr2 = arr.concat();  
        
      • Array.prototype.slice()

        let arr = [1, 3, {username: ' kobe'}];
        let arr2 = arr.slice();
        
    • 深拷贝

      将一个对象从内存中完整的拷贝一份出来

      • JSON.parse(JSON.stringify())
      let obj1={name:1}
      //正则和函数会出问题
      let obj2 = JSON.parse(JSON.stringify(obj1));
      
  17. JSON.parseJSON.stringify

    • 常见问题

      • 如果返回 undefined那么代表这个字段不会被序列号或者反序列化
    • JSON.stringufy执行顺序

      1. 如果对象存在 toJSON() 方法而且能通过它取得有效的值,则调用该方法。否则,返回对象本身。
      2. 如果提供了第二个参数,应用这个函数过滤器。传入函数过滤器的值是第(1)步返回的值。
      3. 对第(2)步返回的每个值进行相应的序列化。
      4. 如果提供了第三个参数,执行相应的格式化。
    • JSON.stringufy第二个参数是过滤传入的对象的属性

    • JSON.parse第二个参数格式化传出的对象

  18. cors

    • 不能发送和接收 cookie,如果要发送 cookie,前端设置xhr.withCredentials = true

    • 调用 getAllResponseHeaders() 方法总会返回空字符串。

    • 简单请求

      • 限制条件

        • 不能使用 setRequestHeader() 设置自定义头部。
        • 只能简单请求POSTGETHead
      • 流程

        • Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)

        • Access-Control-Allow-Origin:它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

        • Access-Control-Allow-Credentials:表示是否允许发送Cookie

        • Access-Control-Expose-Headers:允许设置的header

          graph LR
          A[请求头携带Origin] -->B[响应头返回 cors的参数]
          B -->C{浏览器判断是否符合cors请求}
          C -->|符合| D[正常响应]
          C -->|不符合| E[报跨域的错误]
          

    • 复杂类型

      • 不符合简单类型,就是复杂类型,会先发送预检请求,再发送真正的请求

      • 预检请求

        • 预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的
          • 除了Origin字段,"预检"请求的头信息包括
            • Access-Control-Request-Method:用来列出浏览器的CORS请求会用到哪些HTTP方法
            • Access-Control-Request-Headers:指定浏览器 CORS请求会额外发送的头信息字段
            • Access-Control-Allow-Origin不能用通配符(‘*’)
    • 如果需要携带cookie,那么就要设置withCredentials,并且服务器也要设置Access-Control-Allow-Credentials: true

  19. bind本质是采用了闭包

    function bind(fn, context){
        return function(){
        	return fn.apply(context, arguments);
        };
    }
    
  20. 数据代理

    • 两种方式设置代理

      • Object.defineProperty的方式

        let obj = {
            name: "李白",
            age: 18
        }
        let proxyObj = Object.create({})
        for (let key of Object.keys(obj)) {
            Object.defineProperty(proxyObj, key, {
                set(val) {
                    obj[key] = val
                },
                get() {
                    return obj[key]
                }
            })
        }
        
      • Proxy方式

        let obj = {
            name: "李白",
            age: 18
        }
        let proxyObj = new Proxy(obj, {
            set(obj, key, val) {
                obj[key] = val
            },
            get(obj, key) {
                return obj[key]
            }
        })
        
    • 原理

  21. content-type

    • 常见的三种值

      • application/x-www-form-urlencoded

        • 出现在默认的表单提交、jq默认请求
        • 请求体是formData
        • 所有数据变成键值对的形式 key1=value1&key2=value2
      • multipart/form-data

        • 表单上传文件,fromData上传数据

        • 请求体被分割成多部分,每部分使用 --boundary分割

        • 请求体Request Payload

      • application/json

        • 处理复杂对象
        • 采用JSON字符串的方式传递
        • 请求体Request Payload
  22. 浏览器的渲染流程

    1. GUI 渲染线程

    • 负责渲染浏览器界面,解析 HTML,CSS,构建 DOM 树和 RenderObject 树,布局和绘制等。

    • 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。

    • 注意,GUI 渲染线程与 JS 引擎线程是互斥的,当 JS 引擎执行时 GUI 线程会被挂起(相当于被冻结了),GUI 更新会被保存在一个队列中等到 JS 引擎空闲时立即被执行。

    1. JS 引擎线程

    • Javascript 引擎,也称为 JS 内核,负责处理 Javascript 脚本程序。(例如 V8 引擎)
    • JS 引擎线程负责解析 Javascript 脚本,运行代码。
    • JS 引擎一直等待着任务队列中任务的到来,然后加以处理,一个 Tab 页(renderer 进程)中无论什么时候都只有一个 JS 线程在运行 JS 程序。
    • 注意,GUI 渲染线程与 JS 引擎线程是互斥的,所以如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。

    GUI 渲染线程和JS 引擎线程互斥


  • JS脚本运行和下载都会阻塞DOM的构建
  • JS脚本要修改CSS样式时,会等待CSSOM树创建完毕,再继续运行JS 脚本,所以也就间接的阻塞了 DOM的构建
  • 在没有 JS脚本的影响下, CSSOM构建DOM构建的过程是互不影响的,但是生成render树的过程需要CSSOMDOM都存在,所以CSSOM会阻塞render树生成
  • 在解析页面时候,会预加载 外联URL
  • 假设某些资源加载很慢,浏览器会忽略这些资源接着渲染后面的代码,在chrome浏览器中会先使用预加载器html-repload-scanner先扫描节点中的 srclink等先进行预加载,避免了资源加载的时间
  1. asyncdefer 的区别

    1. deferasync 在网络读取(下载)这块儿是一样的,都是异步的(相较于 HTML 解析)
    2. 如果有多个设置了deferscript标签存在,则会按照顺序执行所有的script
    3. defer脚本会在文档渲染完毕后,DOMContentLoaded事件调用前执行。
    4. DOMContentLoaded事件的触发并不受async脚本加载的影响,onLoad事件受影响

    DOMContentLoaded :仅当 DOM 解析完成后

    onload :资源、DOM渲染完毕

  2. 请求读取返回文件要加responseType: 'arraybuffer',告诉请求函数返回值是文件,不需要做处理