zoukankan      html  css  js  c++  java
  • javascript的基础清单

    javascript的基础清单

    简介


    本文总结了js类型以及以及手写函数等等一些基本内容,目的是为了更好的总结js知识体系,帮助自己更好的掌握js这门语言,字数或许有些多,但请慢慢消化,如有写的不好之处或者写错的地方请指出,作者方便修改

    临近面试,便想要将之前学过的知识系统化再过一遍,深刻的映在脑海中,便有了这次差不多万字的文章,好了,废话不多说,开始撸!

    1.js类型

    首先,现在js的类型有以下几种:

    • 基本类型 : String , Number , null , undefined , Boolean , Symbol , Bigint
    • 引用类型 : object , function

    (1)基本类型和应用类型的区别

    基本数据类型是按值访问的,因为可以直接操作保存在变量中的实际值。示例:

    var a = 10;
    var b = a;
    b = 15;
    console.log(a)//输出10,这说明b只是保存了a复制的一个副本。所以,b的改变,对a没有影响。
    复制代码

    如图: 

    javascript的引用数据类型是保存在堆内存中的对象。实例:

    var obj1 = {};
    var obj2 = obj1;
    obj2.name = "小白";
    console.log(obj1.name); // 小白
    复制代码

    如图: 

    (2)类型转换

    • 1.可以转为false的5个值:NaN,'',0,null,undefined

    • 2.Number([value])把其他数据类型转换我number数字类型

      • 字符串转换为数字:空字符串是0,如果字符串中出现任意一个非有效数字字符,结果都是NaN
      • 布尔转换为数字:true=>1 false=>0
      • null=>0 undefined=>NaN
      • symbol不能转换为数字,报错
      • bigInt可以转换为数字
      • 引用类型(对象或者函数)
      • 首先获取它的[Symbol.toPrimitive]属性值
      • 如果没有这个属性,其次获取它的valueOf
      • 如果还是没有原始值,再转换为字符串toString,然后再转换为数字Number
    • 3.parseInt/parseFloat([value])把其他数据类型转换为数字类型

      • 需要保证[value]是一个字符串,如果不是则首先隐式的把其转换为字符串[value].toString()
      • 从字符串左侧第一个字符开始向右查找,把找到的的有效数字字符,转换为数字(遇到一个非有效数字字符则停止查找,不论后面是否还有有效数字,都不再查找)
      • 如果一个有效数字字符都没有找到,结果都是NaN

    (3)类型检测

    关于类型检测,可以看看我之前写的文章: js四种类型检测方法

    (4)题目测试

    • 1.parseInt计算

    首先这道题考验了parseInt的语法,即parseInt([string],[radix]可选),第一个参数是要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用 ToString 抽象操作)。字符串开头的空白符将会被忽略。第二个参数radix则表示进制。

    let arr = [27.2,0,'0013','14px',123];
    arr = arr.map(parseInt);
    console.log(arr);
    
    //这里的map函数中的结构为
    item       index
    27.2         0
    0            1
    '0013'       2
    '14px'       3
    123          4
    
    由于radix中只接受2-16进制,因此进制为1的直接转化为NaN,
    进制运算举例:
    parseInt('14px',3),首先取出14,然后计算3进制,由于1和4中,4不满足3进制,因此只有1,所以得出1*3^0
    因此结果为[27, NaN, 1, 1, 27]
    复制代码

    2.关于闭包

    • (1)关于闭包

    关于闭包,根据红宝书第3版的解释是:闭包是指有权访问另一个函数作用域中的变量的函数。

    总结:

    • 函数执行会形成一个私有上下文,如果上下文中的某些内容(一般指的是堆内存地址)被上下文以外的些事物(例如:变量、事件绑定)所占用,则当前上下文不能被出栈释放(浏览器的垃圾回收机制GC所决定的) > => 闭包的机制:形成一个不被释放的上下文; • 保护:保护私有上下文中的私有变量和外界互不影响> • 保存:上下文不被释放,那么上下文中的私有变量和值都会保存起来,可以供其下级上下文使用 • 弊端:如果大量使用闭包,会导致栈内存太大,页面渲染变慢,性能受到影响,所以真是项目中想要合理应用闭包;某些代码会导致栈溢出或者内存泄露,这些操作都是需要我们注意的.

    • (2)闭包的运用

    1.解决循环问题

    用var

    var li = document.querySelectorAll("li");
    for(var i=0;i<li.length;i++){
        li[i].onclick= function(){
    		console.log(i);//显示结果都为3
    	}
    }
    复制代码

    使用闭包

    for(var i=0;i<li.length;i++){
    	(function(i){
    		li[i].onclick= function(){
    		console.log(i)	//输出0,1,2
    	}
    	})(i)
    		
    }
    复制代码

    使用let

    for(let i=0;i<li.length;i++){
        li[i].onclick= function(){
    		console.log(i);//显示结果为0,1,2
    	}
    }
    复制代码

    2.compose函数

    当我们遇到const fn1 = x => x*2 , fn2 = x => x+3 , fn3 = x =>x-1 ,想要把处理数据的函数像管道一样连接起来, 然后让数据穿过管道得到最终的结果,如果不通过compose函数处理,则是fn3(fn2(fn1(x))),这样看起来非常不美观。因此我们可以构建一个compose函数,它接受任意多个函数作为参数(这些函数都只接受一个参数)

    function compose(...args){
    		return function(x){
    			let len = args.length;
    			if(args == 0) return x;
    			if(len == 1) return args[0](x);
    			return args.reduceRight((pre,cur)=>{
    				return cur(pre)
    			},x)
    		}
    }
    const fn4 = compose(fn1,fn2,fn3);
    console.log(fn4(5))
    复制代码

    redux中compose源码:

    function compose(...funcs) {
    	if (funcs.length === 0) {
    		return arg => arg
    	}
    	if (funcs.length === 1) {
    		return funcs[0]
    	}
    	return funcs.reduce((a, b) => (...args) => a(b(...args)))
    }
    复制代码

    3.原型链的理解

    关于原型:

    • 1.所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
    • 2.所有函数都有一个prototype(原型)属性,属性值是一个普通的对象
    • 3.所有引用类型的__proto__属性指向它构造函数的prototype

    ![function Person(name){this.name = name}
    let p1 = new Person("小白");
    console.dir(p1)
    
    console.log(p1.__proto__ == Person.prototype)//true
    console.log(Person.prototype.constructor == Person)//true
    复制代码

     

    由此我们可以通过类型.prototype去看他们的API,可以非常清楚的学习到方法的使用,这个可以作为当你忘记api时及时查找的方法 

    关于原型链:当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链

    看Person函数的原型链:

    由图所知实例person会通过__proto__这个属性找到Person.prototype,再由Person.prototype.__proto__往上寻找,一直到null为止,从而形成了一条明显的长链.

    4.手写函数

    (1)new

    new原理:

    • 1.new关键字会首先创建一个空对象
    • 2.将这个空对象的原型对象指向构造函数的原型属性,从而继承原型上的方法
    • 3.将this指向这个空对象,执行构造函数中的代码,以获取私有属性
    • 4.如果构造函数返回了一个对象res,就将该返回值res返回,如果返回值不是对象,就将创建的对象返回

    ES5写法

    function _new(target){
      var obj = {},
          params = [].splice.call(arguments,1),
          result;
    
      obj.__proto__ = target.prototype;
      result = target.apply(obj,params);
    
      if(result!=null && /(function|object)/.test(typeof result)){
        return result;
      }
      return obj;
    }
    复制代码

    ES6写法

    function _new(target,...params){
      let obj = Object.create(target.prototype),
      	  result = target.call(obj,...params);
      if(result!=null && /^(function|object)$/.test(typeof result)){
        return result;
      }
      return obj;
    }
    复制代码

    (2)call

    原理

    • 给CONTEXT设置一个属性(属性名尽可能保持唯一,避免我们自己设置的属性修改默认对象中的结构,例如可以基于Symbol实现,也可以创建一个时间戳名字),属性值一定是我们要执行的函数(也就是THIS,CALL中的THIS就是我们要操作的这个函数)
    • 接下来基于CONTEXT.XXX()成员访问执行方法,就可以把函数执行,并且改变里面的THIS(还可以把PARAMS中的信息传递给这个函数即可);都处理完了,别忘记把给CONTEXT设置的这个属性删除掉(人家之前没有你自己加,加完了我们需要把它删了)
    Function.prototype.call = function(context,...params){
      let key = Symbol('key'),//设置唯一值
          result;
      !/^(object|function)$/.test(typeof context) ? context = Object(context) :null;
      context !=null ? null : context = window;//如果context为null或者undefined,直接赋值为window
    
      context[key] = this;
      result = context[key](...params);//返回值
      delete context[key];
      return result;
    }
    复制代码

    (3)apply

    原理:

    • 基本与call一样,但后面参数应该为数组
    Function.prototype.apply = function(context,params = []){
      let key = Symbol('key'),
          result;
      !/^(object|function)$/.test(typeof context) ? context = Object(context) :null;
      context !=null ? null : context = window;
    
      context[key] = this;
      result = context[key](...params);
      delete context[key];
      return result;
    }
    复制代码

    (4)bind

    原理:

    • bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。
    • 当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。
    • 一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
    Function.prototype.bind = function(context,...params){
    	let self = this;
        return funtion(...innerArgs){
        	params = params.concat(...innerArgs);
            return self.call(context,...params);
        }
    }
    复制代码

    (5)防抖

    思路:

    • 在当前点击完成后,我们等wait这么长的时间,看是否还会触发第二次,如果没有触发第二次,属于非频繁操作,我们直接执行想要执行的函数func:如果触发了第二次,则以前的不算了,从当前这次再开始等待...
    /*
          防抖:
            @params:
              func[function]:最后要触发执行的函数
              wait[number]:频繁设定的界限
              immediate[boolean]:默认多次操作,我们识别的是最后一次,但是immediate=true,让其识别第一次
            @return
              可以被调用执行的函数
     */
    function debounce(func,wait = 300,immediate = false){
          let timer = null;
          return function anonymous(...params){
            let now = immediate && !timer;
    
            //每次点击都把之前设置的定时器清除掉
            clearInterval(timer)
            //重新设置一个新的定时器监听wait事件内是否触发第二次
            timer = setTimeout(() => {
              timer = null;//垃圾回收机制
              //wait这么久的等待中,没有触发第二次
              !immediate ? func.call(this,...params) : null;
            }, wait);
    
            //如果是立即执行
            now ? func.call(this,...params) : null;
          }
    }
    复制代码

    (6)节流

    思路:

    • 函数节流:在一段频繁操作中,可以触发多次,但是触发的频率由自己指定
    /*
          @params:
              func[function]:最后要触发执行的函数
              wait[number]:触发的频率
            @return
              可以被调用执行的函数
    */
    function throttle(func,wait = 300){
          let timer = null,
              previous = 0;//记录上一次操作时间
          return function anonymouse(...params){
            let now = new Date(),//记录当前时间
                remaining = wait - (now - previous);//记录还差多久达到我们一次触发的频率
            if(remaining <= 0){
              //两次操作的间隔时间已经超过wait了
              window.clearInterval(timer);
              timer = null;
              previous = now;
              func.call(this,...params);
            }else if(!timer){
              //两次操作的间隔时间还不符合触发的频率
              timer = setTimeout(() => {
                timer = null;
                previous = new Date();
                func.call(this,...params);
              }, remaining);
            }
          }
    }
    复制代码

    (7)浅拷贝

    数组的浅拷贝:

    • 1.数组的slice方法
    let arr = [1,2,3]
    let newArr = arr.slice();
    复制代码
    • 2.concat
    let arr = [1,2,3]
    let newArr = arr.concat();
    复制代码

    对象浅拷贝 [注意]这里的toType请参考我写的jquery工具方法,链接: jquery工具方法

    function clone(obj){
          var type = toType(obj);
          if(/^(boolean|number|string|null|undefined|bigInt|symbol)$/.test(type)) return obj;
          if(/^(regExp|date)$/.test(type)) return new obj.constructor(obj);
          if(/^(function)$/.test(obj)) return function(){
            obj()
          };
          if(/^error$/.test(type))return new obj.constructor(obj.message);
          var target = {},
              keys = this.getOwnProperty(obj); 
          Array.isArray(target) ? target = [] : null;
          
         keys.forEach(item=>{
           target[item] = obj[item]
         })
          return target
    }
    复制代码

    (8)深拷贝

    • 运用JSON.stringify+JSON.parse

    [注意]这个方法有缺陷:

    • 正则会被处理为空对象
    • 具备函数/symbol/undefined属性值直接被干掉
    • BigInt还处理不了,会报错
    • 日期对象最后还是字符串
    let obj = {
          a:1,
          b:/^$/,
          c:undefined,
          d:new Date()
    }
    console.log(JSON.parse(JSON.stringify(obj)));
    复制代码

    如图: 

    手写深克隆

    function deepClone(obj,cache = new Set()){//深克隆 cache处理self属性,防止死递归
          //只有数组和对象我们再处理深克隆,其余的按照浅克隆
          let type = toType(obj);
          if(!/^(array|object)$/.test(type)) return this.clone(obj);
    
          if(cache.has(obj)) return obj;
          cache.add(obj);
    
          let keys = this.getOwnProperty(obj),
            clone = {};
          type === "array" ? clone = [] : null;
    
          keys.forEach(key=>{
            clone[key] = this.deepClone(obj[key],cache)
          })
          return clone
    }
    复制代码

    (9)instanceof

    function _instanceof(L,R){    
        // 验证如果为基本数据类型,就直接返回false
        const baseType = ['string', 'number','boolean','undefined','symbol']
        if(baseType.includes(typeof(L))) { return false }
        
        let RP  = R.prototype;  //取 R 的显示原型
        L = L.__proto__;       //取 L 的隐式原型
        while(true){           // 无线循环的写法(也可以使 for(;;) )
            if(L === null){    //找到最顶层
                return false;
            }
            if(L === RP){       //严格相等
                return true;
            }
            L = L.__proto__;  //没找到继续向上一层原型链查找
        }
    }
    复制代码

    (10)数组扁平化

    flat方法实现

    let arr = [1,2,[3,4,[5,[6]]]]
    console.log(arr.flat(Infinity))//flat参数为指定要提取嵌套数组的结构深度,默认值为 1
    复制代码

    reduce实现

    function fn(arr){
       return arr.reduce((prev,cur)=>{
          return prev.concat(Array.isArray(cur)?fn(cur):cur)
       },[])
    }
    复制代码

    5.浅谈js事件

    关于事件,我们认为事件是可以被Javascript侦测到的行为,通俗的讲就是当用户与Web页面进行某些交互时,解释器就会创建响应的event对象以描述事件信息。【注意:事件并不是你自己添加才有的,而是浏览器自带的】

    (1).事件绑定

    • 直接在html中定义事件
    <button onclick="console.log(1)">点我</button>
    复制代码

    缺点:违反了内容与行为相分离的原则,在html中写js代码,强耦合,不利于复用代码。不推荐

    • DOM0级事件
    let btn = document.querySelector(".btn");
    btn.onclick = function(){
    	console.log(1);
    }
    复制代码

    缺点:解决了强耦合性,但只能给一个事件对象绑定一个事件类型。 同样的事件类型,绑定两次,以 后一次 为准。

    -DOM2级事件

    同样的事件类型,绑定多次,每个都会执行,可以设置时捕获还是冒泡。

    let btn = document.querySelector(".btn");
    btn.addEventListener("click",function(){
    	console.log(1);
    },false)//默认值为false,可不写,false表示冒泡
    复制代码

    冒泡与捕获

    事件冒泡即事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点,事件捕获思想是不太具体的DOM节点应该更早接收到事件,而最具体的节点应该最后接收到事件

    事件冒泡

    <div class="my">click me</div>
    如果你点击了页面中的<div>元素,那么这个click事件会按照如下顺序传播:
    1.<div>
    2.<body>
    3.<html>
    4.document
    复制代码

    事件捕获

    <div class="my">click me</div>
    如果你点击了页面中的<div>元素,那么这个click事件会按照如下顺序传播:
    1.document
    2.<html>
    3.<body>
    4.<div>
    复制代码

    事件流

    事件流包括三个阶段:事件捕获阶段,处于捕获阶段,事件冒泡阶段

    以前面的html为例,如图所示: 

    7.最后总结

    终于写完这篇文章了,写文章虽然一个一个字敲的有点累,并且屁股坐的有些痛了,但是最后能把文章写完,也是有不少的心得。写这篇文章时,真的是重新把自己掌握不牢固的知识又回顾了一遍,写完也是依依不舍。本来在写的时候还想要加关于http的知识的,但是发现自己现在http知识还不够牢固,也就不献丑了,作者现在准备狠狠的去补补这方面的知识,以后等感觉掌握的差不多就写一篇关于http独立的文章吧!最后,谢谢各位读者,希望你们从本文章得到一些收获,也欢迎大家在下方评论,一起探讨知识。如果你喜欢该文章,请各位师哥美女动动你的拇指点个赞

  • 相关阅读:
    在谷歌地图上绘制行政区域轮廓【结合高德地图的API】
    用PL/SQL远程连接Oracle服务器
    找工作之离职篇
    linux设置定时备份mysql数据库
    使用NoSQL实现高并发CRM系统实践(源代码+解析)
    做领导应该注意的几个问题
    如何才能成为真正的程序员
    利用websocket实现手机扫码登陆后,同步登陆信息到web端页面
    利用laravel-echo主动向服务端发送消息,实现在线状态管理
    飞鱼CRM
  • 原文地址:https://www.cnblogs.com/wsj1/p/14005717.html
Copyright © 2011-2022 走看看