• JavaScript理论篇1之基础理论


    1、JS面向对象的理解
    1)一切皆对象,类是对象的一个抽象集合,对象是类的实例化
    2)面向对象三大特征:封装,继承,多态
    1】封装
    关于属性和方法,封装在函数里面,外部不可见,感知不到
    2】继承
    es6之前没有类的概念,是通过原型链来实现继承,es6通过class实现继承
    3】多态(覆盖、重载)
    定义:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
    a、覆盖(子类重写父类方法)
    原型链上面的同名函数(方法),子类会覆父类的
    b、重载
    同一函数,参数不同(个数和类型),执行结果不同
    JS没有严格意义上的重载,需要通过其他手段去实现,比如通过arguments对象去判断

    *注:函数重载(多态的一种体现)
    函数名相同,函数的参数不同(包括参数个数和参数类型),根据参数的不同去执行不同的操作
    注意:JavaScript 中没有真正意义上的函数重载
    1)通过 arguments 对象来实现
    arguments 对象,是函数内部的一个类数组对象,它里面保存着调用函数时,传递给函数的所有参数。
    function overload () {
    if (arguments.length === 1) {
    console.log(‘一个参数’)
    }
    if (arguments.length === 2) {
    console.log(‘两个参数’)
    }
    }
    overload(1); //一个参数
    overload(1, 2); //两个参数

    2)jQuery 中的 css( ) 方法
    jQuery 的 css( ) 方法就是通过判断参数的类型,来确定执行什么操作

    3)总结
    虽然 JavaScript 并没有真正意义上的重载,但是重载的效果在JavaScript中却非常常见,比如 数组的 splice( )方法,一个参数可以删除,两个参数可以删除一部分,三个参数可以删除完了,再添加新元素。 再比如 parseInt( )方法 ,传入一个参数,就判断是用十六进制解析,还是用十进制解析,如果传入两个参数,就用第二个参数作为数字的基数,来进行解析

    2、JS变量提升
    1】定义
    JS引擎在读取JS代码时,会先通篇扫描所有的代码,然后把所有声明变量声明提升到当前执行环境的最顶端。全局变量提升至该组件的最顶部,函数变量提升至函数的最顶部。
    2】例子
    var test = “outside”;
    function f1() {
    console.log(test); //undefined
    if (false) {
    var test = “inside”;
    }
    console.log(test); //undefined
    }
    function f2() {
    console.log(test); //undefined
    var test = “inside”;
    console.log(test); //inside
    }

    3、JS函数提升
    类似变量,函数声明也会被提升至代码顶部。
    1)JS中,函数是第一公民。函数提升优先级比变量提升还高。
    2)函数声明才会提升,函数表达式则不会。
    3)如果是两个函数声明,出现在后面的函数声明可以覆盖前面的。
    function t1(age) {
    console.log(age);
    var age = 27;
    console.log(age);
    function age() {}
    console.log(age);
    }
    t1(3);
    等价于:
    function t1(age) {
    var age = function () {}
    console.log(age);
    var age = 27;
    console.log(age);
    console.log(age);
    }

    4、JS类型的隐式转换和显式转换
    1】隐式转换(弱类型的标志之一)
    不同数据类型的数据运算时,会进行一个隐式的转换
    1)字符串和数字
    “+” 操作符,如果有一个为字符串,那么都转化到字符串然后执行字符串拼接
    “-” 操作符,转换为数字,相减 (-a, a * 1 a/1) 都能进行隐式强制类型转换
    [] + {} 和 {} + []
    2)布尔值到数字
    1 + true = 2
    1 + false = 1
    !! 运算符可以将右侧的值强制转换为布尔值,这也是将值转换为布尔值的一种简单方法

    2】显式转换
    显示转换一般指使用Number、String和Boolean三个构造函数,手动将各种类型的值,转换成数字、字符串或者布尔值。
    Number(‘’) // 0
    Number(true) // 1
    Number(null) // 0
    Number(undefined) // NaN
    3】等于 和 严格等于
    == 在表达式两边的数据类型不一致时,会隐式转换为相同数据类型,然后对值进行比较。
    === 不会进行类型转换,在比较时除了对值进行比较以外,还比较两边的数据类型。
    ===的缺陷:===的NaN不等于自身,以及+0等于-0(用es6的Object.is()解决)
    4】小例子
    a、如何让 (a == 1 && a == 2 && a == 3) 的值为true?
    let a = [1,2,3]
    a.join = a.shift
    b、把字符串型的数字转化为真正的数字

    • parseInt:
      parseInt() 函数可解析一个字符串,并返回一个整数
      parseInt(“10”); //返回 10
      parseInt(“19”,10); //返回 19 (10+9)
    • Number
    • 通过运算来转换:
      js的弱类型的特点,只进行了算术运算,实现了字符串到数字的类型转换

    5、null、NaN和undefined的区别
    未定义的值和定义未赋值的为undefined
    null表面上代表空对象,null是一种特殊的object
    NaN是一种特殊的number
    undefined与null是相等(严格=的时候不相等);NaN与任何值都不相等,与自己也不相等
    **undefined 与 undeclared 的区别?
    已在作用域中声明但还没有赋值的变量,是undefined。相反,还没有在作用域中声明过的变量,是 undeclared 的。

    6、js判断数据类型
    1】typeof

    能判断:number、boolean、symbol、string、object、undefined、function
    缺陷:判断引用类型,除了function返回function类型外,其他均返回object
    判断Array和null都返回object
    2】toString(较为完美的判断方法)
    toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型
    Object.prototype.toString.call(‘’) ; // [object String]
    Object.prototype.toString.call(1) ; // [object Number]
    Object.prototype.toString.call(true) ; // [object Boolean]
    Object.prototype.toString.call(Symbol()); //[object Symbol]
    Object.prototype.toString.call(undefined) ; // [object Undefined]
    Object.prototype.toString.call(null) ; // [object Null]
    Object.prototype.toString.call(new Function()) ; // [object Function]
    Object.prototype.toString.call(new Date()) ; // [object Date]
    Object.prototype.toString.call([]) ; // [object Array]
    Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
    Object.prototype.toString.call(new Error()) ; // [object Error]
    Object.prototype.toString.call(document) ; // [object HTMLDocument]
    Object.prototype.toString.call(window) ; //[object global] window 是全局对象 global 的引用
    3】instanceof
    原理:instanceof 可以判断一个引用是否属于某构造函数;另外,还可以在继承关系中用来判断一个实例是否属于它的父类型。
    instanceof的判断逻辑是: 从当前引用的proto一层一层顺着原型链往上找,能否找到对应的prototype。找到了就返回true。
    instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false,例如:
    1 instanceof Number
    true instanceof Boolean
    运行机制:
    假设现在有 x instanceof y 一条语句,则其内部实际做了如下判断:
    while(x.proto!==null) {
    if(x.proto===y.prototype) {
    return true;
    }
    x.proto = x.proto.proto__;
    }
    if(x.proto==null) {return false;}
    复制代码
    x会一直沿着隐式原型链__proto__向上查找直到x.proto.proto…===y.prototype为止,如果找到则返回true,也就是x为y的一个实例。否则返回false,x不是y的实例。
    4】constructor属性
    查看对象对应的构造函数
    object的每个实例都具有属性constructor
    用法:实例.constructor

    缺点:Undefined和Null类型不能判断

    *****JavaScript中如何检测一个变量是一个String类型?请写出函数实现
    typeof(obj) === “string”
    typeof obj === “string”
    obj.constructor === String

    7、new
    1】new 运算符:创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例
    2】语法:new constructor[([arguments])]
    constructor:一个指定对象实例的类型的类或构造函数
    3】意义:
    1。不用手动新建一个obj ,new会帮你创建
    2。不用把新建的obj的__proto__指向构造函数的prototype,new会帮你做。
    3。构造函数this的作用域会指向实例本身。
    4。不用手动return新建的obj,new会帮你return。
    5。new出来的实例的__proto__会指向构造函数的prototype;构造函数的方法,实例可以直接调用。
    6。实例的constructor属性指向构造函数。
    4】new一个函数和直接调用函数的区别
    1)new函数将会返回一个该函数的实例对象
    若返回值为基本类型,则不会返回;引用类型才会有返回值
    2)直接调用函数返回的是该函数的返回值,若无返回值,则返回undefined


    new之后发生的过程:
    1】实例化了一个obj,并return了回来
    2】实例化的obj的prototype,不指向构造函数,而是指向构造函数的原型对象
    3】实例化的obj的constructor属性指向构造函数本身
    4】构造函数本身 this的作用域会指向实例本身

    8、JS创建和调用函数的几种方式(3种定义/4种调用)
    1】创建函数
    1)函数声明: 
    此方法创建的函数,具有函数提升的效果,可以前面调用,后面在声明
    function sum1(num1,num2){
       return num1+num2;
    }
    2)函数表达式 或 函数字面量:
    函数表达式可以自调用,声明的函数不能自调用
    (function (){
    var h = ‘专业始于专注’;
    document.write(h);})(); //正常执行
    function fnName(){
      console.log(123);
    }() // 报错
    var fnName = function () {
      console.log(123);
    }() // 正常执行
    3)new Function() 实例化函数:
    语法:
    var function_name = new Function(arg1, arg2, …, argN, function_body)
    在上面的形式中,每个 arg 都是一个参数,最后一个参数是函数主体(要执行的代码)。这些参数必须是字符串
    所有函数都应看作 Function 类的实例
    var sum = new Function(“num1,num2”,“return num1+num2”);
    var result1 = sum(120,130);

    2】函数调用
    1)函数调用模式(直接函数名+())
    this指向window

    2)方法调用模式
    先定义一个对象,然后在对象的属性中定义方法,通过myobject.property来执行方法,this指向自身这个obj

    3)构造器调用模式
    定义一个函数对象,在对象中定义属性,在其原型对象中定义方法
    在使用prototype的方法时,必须使用new实例化该对象才能调用其方法

    4)apply,call和bind调用模式
    详情见12

    ****函数的长度属性:length 属性指明函数的形参个数(没有默认值的)
    arguments是一个特殊的对象,第1个参数的属性名是’0’,第2个参数的属性名是’1’,以此类推,并且它还有length属性,存储的是当前传入函数参数的个数,很多时候我们把这种对象叫做类数组对象。

    9、JS创建对象的几种方式
    1】对象直接量(对象字面量)
    var objectName = {
    属性名1 : 属性值1,

    属性名n : 属性值n
    };
    创建单个对象没问题,创建多个相同类型的对象会造成代码重复
    本质上是 new Object() 的语法糖

    2】构造对象
    使用 new 运算符调用构造函数,可以构造一个实例对象
    var o = new Object(); //定义一个空对象
    var a = new Array(); //定义一个空数组
    var f = new Function(); //定义一个空函数

    3】Object.create
    Object.create 是 ECMAScript 5 新增的一个静态方法,用来创建一个实例对象
    该方法可以指定对象的原型和对象特性。具体用法如下:
    Object.create(prototype, descriptors)
    下面示例使用 Object.create定义一个对象,继承 null,包含两个可枚举的属性 size 和 shape,属性值分别为 “large” 和 “round”。
    var newObj = Object.create (null, {
    size : { //属性名
    value : “large”, //属性值
    enumerable : true //可以枚举
    },
    shape : { //属性名
    value : “round”, //属性值
    enumerable : true //可以枚举
    }
    });

    4】工厂模式
    function createPerson(){
    var o=new Object();
    o.name=name;
    o.sex=sex;
    o.sayName=function(){
    alert(this.name);
    }
    return o;
    }
    var person1=new createPerson(‘zhangsan’,‘男’); //实例化person1
    var person2=new createPerson(‘wangwu’,‘女’); //实例化person2
    特点:
    1、用函数来封装,并以特定接口创建对象;
    2、有返回值
    缺点:
    工厂模式解决了重复实例化多个对象的问题,但没有解决对象识别的问题(但是工厂模式却无从识别对象的类型,因为全部都是Object,不像Date、Array等,本例中,得到的都是obj对象,对象的类型都是Object,因此出现了构造函数模式)

    5】构造函数模式
    当任意一个普通函数用于创建一类对象时,它就被称作构造函数,或构造器
    构造函数特点:
    1、首字母必须大写,用来区分于普通函数(不大写的话,也没事)
    2、构造函数的this,是指向实例化后的对象,而不是构造函数自己
    3、在函数内部对新对象(this)的属性进行设置,通常是添加属性和方法
    4、构造函数可以包含返回语句(不推荐),但返回值必须是this,或者其它非对象类型的值
    5、使用New来生成实例对象
    function Person(name,age,family) {
    this.name = name;
    this.age = age;
    this.family = family;
    this.say = function(){
    alert(this.name);
    }
    }
    var person1 = new Person(“lisi”,21,[“lida”,“lier”,“wangwu”]);
    对比工厂模式有以下不同之处:
    1、没有显式地创建对象
    2、直接将属性和方法赋给了 this 对象
    3、没有 return 语句
    以此方法调用构造函数步骤 :
    1、创建一个新对象
    2、将构造函数的作用域赋给新对象(将this指向这个新对象)
    3、执行构造函数代码(为这个新对象添加属性)
    4、返回新对象 ( new实例化的时候返回的 )
    优点与缺点:
    优点:能够通过instanceof识别对象类型
    缺点:每次实例化一个对象,都会把属性和方法复制一遍

    6】原型模式
    function Person() {
    }
    Person.prototype.name = “lisi”;
    Person.prototype.age = 21;
    Person.prototype.family = [“lida”,“lier”,“wangwu”];
    Person.prototype.say = function(){
    alert(this.name);
    };
    console.log(Person.prototype); //Object{name: ‘lisi’, age: 21, family: Array[3]}
    var person1 = new Person(); //创建一个实例person1
    console.log(person1.name); //lisi
    var person2 = new Person(); //创建实例person2
    person2.name = “wangwu”;
    person2.family = [“lida”,“lier”,“lisi”];
    优点:
    原型模式的好处是所有对象实例共享它的属性和方法(即所谓的共有属性),此外还可以设置实例自己的私有属性(方法),可以覆盖原型对象上的同名属性(方法)

    7】混合模式(构造函数模式+原型模式)
    构造函数模式用于定义实例的私有属性,原型模式用于定义和共享的共有属性和方法,最大限度的节省了内存

    10、介绍JS原型
    1】前景
    a、JavaScript是面向对象的编程语言,可以继承属性和方法
    b、es6之前,JavaScript没有 类 的概念,暂时无法实现通过类的继承
    c、JavaScript的继承就是基于原型链的继承

    2】定义
    a、原型其实就是一种对象,简称原型对象
    b、每当创建一个函数的时候,这个函数都默认创建一个不可见的属性 prototype,叫隐式原型,一般用__proto__来表示(prototype是函数的属性,一般的对象是没有的,而且不可以直接读取),指向一个原型对象,简称函数的原型;这个原型对象默认会有一个属性constructor 指向了这个函数,或者说这个原型对象的constructor属性值就是这个函数

    3】使用new + 构造函数 实例化对象
    构造函数使用new实例化对象的时候,那么这个对象就会存在一个默认的不可见的属性,来指向了构造函数的原型对象。 这个不可见的属性我们一般用 [[prototype]] 或者 proto 来表示,只是这个属性没有办法直接访问到。
    实例对象的constructor指向构造函数

    说明:
    1)通过new实例化对象后,这个新的p1对象其实已经与Person构造函数没有任何关系了,p1对象的[[ prototype ]]属性,指向的是Person构造函数的原型对象
    2)如果我们访问p1中的一个属性name,如果在p1对象中找到,则直接返回;如果p1对象中没有找到,则直接去p1对象的[[prototype]]属性指向的原型对象中查找,如果查找到则返回;如果原型中也没有找到,则继续向上找原型的原型(原型链)
    3)p1对象的[[prototype]]属性不可直接访问,在浏览器可以用__proto__这个属性代替
    4)通过p1对象只能读取原型中的属性name的值,而不能修改原型中的属性name的值;但是p1对象可以给自己添加一个同名的属性name,我们这里可以称之为私有属性

    4)新实例化出来的对象的constructor属性则不再指向Person构造函数了

    4】一些属性和方法
    1)proto 属性(注意:左右各是2个下划线)
    用构造方法创建一个新的对象之后,这个对象中默认会有一个不可访问的属性 [[prototype]] , 这个属性就指向了构造方法的原型对象。
    ​ [[prototype]]属性不可直接访问,但是在个别浏览器中,也提供了对这个属性[[prototype]]的访问(chrome浏览器和火狐浏览器。ie浏览器不支持)。访问方式:p1.proto
    ​ 但是开发者尽量不要用这种方式去访问,因为操作不慎会改变这个对象的继承原型链

    2)hasOwnProperty() 方法
    hasOwnProperty方法,可以判断一个属性是否来自对象本身,还是来自这个对象的[[prototype]]属性指向的原型
    3)in 操作符
    ​in操作符用来判断一个属性是否存在于这个对象中。但是在查找这个属性时候,现在对象本身中找,如果对象找不到再去原型中找
    如果一个属性存在,但是没有在对象本身中,则一定存在于原型中

    5】构造函数实例化对象的缺陷
    构造函数的属性,对于每一个实例化对象而言,各自都可以拥有私有属性和方法,但是实际情况很多属性和方法是可以共有的,这就造成了内存的浪费
    使用:混合模式(构造函数模式+原型模式)可以解决这个问题
    构造函数模式用于定义实例的私有属性,原型模式用于定义和共享的共有属性和方法,最大限度的节省了内存

    6】动态原型模式
    把构造方法和原型分开写,总让人感觉不舒服,应该想办法把构造方法和原型封装在一起,所以就有了动态原型模式。
    ​ 动态原型模式把所有的属性和方法都封装在构造方法中,而仅仅在需要的时候才去在构造方法中初始化原型,又保持了同时使用构造函数和原型的优点。
    注意:构造函数的this,是指向实例化后的对象,而不是构造函数自己

    比如上面实例化后的p1对象,本身是没有eat这个方法的,如果直接调用这个eat的方法的话,那么它的类型肯定不是function,所以由此判断之后,可以给它指向原型对象的eat方法

    6】原型的实际应用
    jquery用的比较多
    $p.css(‘color’, ‘red’); //css是原型方法
    $p.html(); //html是原型方法

    7】如何体现原型的扩展性
    插件机制
    为何要把原型方法放在 . f n ? : 扩 展 插 件 ( 第 一 , 构 造 函 数 的 p r o t o t y p e 肯 定 得 指 向 能 扩 展 的 对 象 ; 第 二 , .fn?:扩展插件(第一,构造函数的 prototype 肯定得指向能扩展的对象;第二, .fnprototype.fn 肯定得指向能扩展的对象)
    好处:
    a,只有$会暴露在window全局变量
    b,将插件扩展统一到 $.fn.xxx 这一个接口,方便使用

    11、介绍JS原型链(原型链解决的是继承问题吗?是的)
    1】原型链可以简单理解为:访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链(__proto__跟[[prototype]]其实是同一个东西,只不过[[prototype]]这个属性个别浏览器不支持)
    2】如果我们访问一个名为p1的对象中的一个属性name,如果在p1对象中找到,则直接返回;如果p1对象中没有找到,则直接去p1对象的[[prototype]]属性指向的原型对象中查找,如果查找到则返回;如果原型中也没有找到,则继续向上找原型的原型。这里原型的原型,其实就组成了原型链
    3】原型链的最顶层是Object.prototype;原型链的终点是null,原因是:一方面,你没法访问null的属性,所以起到了终止原型链的作用;另一方面,null在某种意义上也是一种对象,即空对象,不会违反“原型链上只能有对象”的约定

    4】原型链的缺陷
    问题一:当原型链中包含引用类型值的原型时,该引用类型值会被所有实例化对象共享
    问题二:在创建子类型(例如创建Son的实例)时,不能向超类型(例如Father)的构造函数中传递参数
    最佳解决方法:动态原型模式(见上)
    5】原生继承的小例子

    注意:原型链上面的同名函数(方法),子类会覆父类的(多态的覆盖特性)

    12、异步和单线程
    1)单线程
    单线程:只有一个线程,只能做一件事
    2)什么情况下需要用异步
    1】需要等待的情况,比如ajax请求,文件上传下载,动态img加载
    2】不能像alert那样阻塞主线程
    2)JS单线程异步
    因为JavaScript的单线程,因此同个时间只能处理同个任务,所有任务都需要排队,前一个任务执行完,才能继续执行下一个任务。
    因此,JavaScript在设计的时候,就已经考虑到这个问题,主线程可以完全不用等待文件的读取完毕或ajax的加载成功,可以先挂起处于等待中的任务,先运行排在后面的任务,等到文件的读取或ajax有了结果后,再回过头执行挂起的任务,因此,任务就可以分为同步任务和异步任务。
    3)前端使用异步的场景

    4)setTimeout
    JavaScript 主线程拥有一个执行栈以及一个任务队列。JavaScript是单线程执行的,无法同时执行多段代码。当某一段代码正在执行的时候,所有后续的任务都必须等待,形成一个队列。一旦当前任务执行完毕,再从队列中取出下一个任务,这也常被称为 “阻塞式执行”。
    setTimeout( function(){ alert(’你好!’); } , 0); //不会立即执行
    如上,如果代码中设定了一个 setTimeout,那么浏览器便会在合适的时间,将代码插入任务队列,如果这个时间设为 0,就代表立即插入异步队列,但不是立即执行,仍然要等待前面代码执行完毕。所以 setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。

    13、为什么js是单线程而不是多线程?
    避免DOM渲染的冲突
    最初作为浏览器脚本语言,用于操作DOM,这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
    为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。

    14、JS事件轮询怎么实现

    15、JS的严格模式
    1】为什么使用严格模式
    消除代码运行的一些不合理、不安全之处,保证代码运行的安全;
    提高编译器效率,增加运行速度;
    为未来新版本的Javascript做好铺垫
    2】严格模式有哪些
    1)不允许使用未声明的变量
    2)不允许删除变量、对象(包括函数)、不允许删除的属性
    3)不允许变量重名
    4)不允许对只读属性赋值
    5)不允许对一个使用getter方法读取的属性进行赋值
    6)不允许使用八进制
    7)不允许使用转义字符
    8)变量名不能使用 “eval” 字符串, eval() 创建的变量不能被调用
    9)禁止this关键字指向全局对象

    16、什么是纯函数,有什么作用(用于react比较多)
    1】返回结果只依赖于它的参数
    2】在执行过程里面没有副作用(内部变量和对象,外部程序根本观察不到,不会对外部造成影响)
    在JavaScript中可以很容易地创建全局变量,这些变量在很多的函数中都能访问修改,这也是导致发生bug的常见原因。并且难以调试。此时,纯函数就能极大地降低bug出现的概率。因为:
    ①纯函数可以很容易的进行单元测试(不需要考虑上下文,只考虑输入输出)
    ②纯函数具有健壮性,改变执行次序并不会对系统造成影响,一次纯函数可以并行执行。
    ③更好的管理状态,是可预测性增强,降低代码管理的难度。
    此外,纯函数更大的威力是在高级框架之中:组件化开发状态共享。(例如vue/React)
    以上几点可以称之为“函数式编程思想”
    函数式编程:
    1)纯函数
    2)函数柯里化

    17、getter 与 setter
    1】对象属性
    对象有两种属性:
    1)数据属性,就是我们经常使用的属性
    2)访问器属性,也称存取器属性
    2】存取器属性
    存取器属性就是一组获取和设置值的函数
    getter负责获取值,它不带任何参数
    setter负责设置值,在它的函数体中,一切的return都是无效的
    3】特点
    在对象内如果设置了存取器属性,如果某一变量只声明了getter方法,那么它仅仅只可读而不可写。如果只声明了setter方法,那么读到的该变量值永远都是undefined

    18、高阶函数
    1】定义
    输入的参数里面有一个或者多个函数,输出结果也是函数。
    在js里面主要是利用闭包实现的,最简单的就是经常看到的在一个函数内部输出另一个函数
    var add = function() {
    var num = 0;
    return function(a) {
    return num = num + a;
    }
    }
    add()(1); // 1
    add()(2); // 2
    这里的两个add()(1)和add()(2)不会互相影响,可以理解为每次运行add函数后返回的都是不同的匿名函数,就是每次add运行后return的function其实都是不同的,所以运行结果也是不会影响的。
    2】实际应用
    最常见的高阶函数就是Promise。

    19、JS是什么范式语言(面向对象还是函数式编程)
    JavaScript语言也支持多范式编程。我们常常混合使用这些范式来完成一些大型的Web项目的开发。JavaScript支持的编程范式有:
    1)命令式
    2)函数式
    3)面向对象
    1】命令式(Imperative JavaScript)
    命令式可以理解成面向过程,是简单的从上至下完成任务,流水账式的编程风格。如下图所示代码:
    这样编写代码比较简单,但存在一些明显的问题,比喻可能存在函数名描述性差、命名冲突、变量冗余、复用性差等。
    2】函数式(Functional JavaScript)
    示例代码如下:
    可以看到,通过数组对象的内置原型方法map和reduce,大大简化了代码,并且如果熟悉了这种风格,其代码的自描述性也很棒。
    map和reduce都是高阶函数(能接受函数作为参数,也能返回函数对象)。map函数用来把一组数据依次变换成另外一组数据,而reduce用来汇总数据,下图是对这两个关键函数有趣而直白的描述:
    3】面向对象(Object-Oriented JavaScript)
    面向对象的核心思想是把任务抽象成一个(或一组)对象的数据和操作,在C++语言中,这些抽象出来的对象可以便于接口或实现的复用(继承)以及访问控制,在JS中由于没有接口的概念,只能是函数实现的复用。
    示例代码如下:
    上面的代码创建了一个Task对象,包含公开属性nums,并通过原型定义了2个方法:square和sum,可以在多个对象之间共享,这样将节约内存。另外代码被包含在一个IIAF(immediately-invoked function expression)中,而不再是全局的。除了更符合OO的思维方式外,似乎代码并没有变得更为简洁。

    20、JS常见的三种设计模式
    1)工厂模式(工厂流水线模式,批量实例化具有相同属性和方法的对象)
    1】以下几种应用情景 工厂模式特别适用:
    a、对象的构建十分复杂
    b、需要依赖具体环境创建不同实例(比如构造器的大类是人,实例化的对象要分姓名年龄和性别)
    c、处理大量具有相同属性和方法的小对象
    2】缺陷
    工厂模式解决了重复实例化多个对象的问题,但没有解决对象识别的问题(但是工厂模式却无从识别对象的类型,因为全部都是Object,不像Date、Array等,本例中,得到的都是obj对象,对象的类型都是Object,因此出现了构造函数模式)

    function CreatePerson(name,age,sex) {
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sex = sex;
    obj.sayName = function(){
    return this.name;
    }
    return obj;
    }
    var p1 = new CreatePerson(“longen”,‘28’,‘男’);
    var p2 = new CreatePerson(“tugenhua”,‘27’,‘女’);
    console.log(p1.name); // longen
    console.log(p1.age); // 28
    console.log(p1.sex); // 男
    console.log(p1.sayName()); // longen

    console.log(p2.name); // tugenhua
    console.log(p2.age); // 27
    console.log(p2.sex); // 女
    console.log(p2.sayName()); // tugenhua

    2)观察者模式

    1】定义
    定义对象间的一种一对多的依赖关系:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
    观察者模式必须包含两个角色:观察者和目标对象。
    2】优点
    a、观察者和被观察者是松耦合关系(低耦合)。
    b、建立统一的一套触发机制。
    3】缺点
    a、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 b、如果在观察者和观察目标之间有循环依赖的话,触发它们可能导致系统崩溃。 c、观察者不知道所观察的目标对象是怎么发生变化的,仅仅只知道观察目标发生了变化。
    4】应用场景
    比如最常见的button按钮监听单击事件,就涉及到观察者模式。button是被观察者,OnClickListener是观察者。

    3)发布订阅模式
    1】定义
    发布-订阅模式里面包含了三个模块:发布者,订阅者和消息调度中心。一但发布者更新内容,它会通过调度中心去将信息广播给订阅它的一些订阅者。

    2】观察者模式和发布订阅模式的区别
    a、观察者模式中的观察者和被观察者是松耦合关系,而发布订阅模式中的发布者和订阅者不存在耦合关系(完全解耦),因为消息的通知是由调度中心去处理。
    b、在开发大型项目的时候,订阅/发布模式会让业务更清晰。
    3】demo
    var event = new Event() // 创建event实例
    // 定义一个自定义事件:“load”
    function load (params) {
    console.log(‘load’, params)
    }
    event.addEventListener(‘load’, load)
    // 再定义一个load事件
    function load2 (params) {
    console.log(‘load2’, params)
    }
    event.addEventListener(‘load’, load2)
    // 触发该事件
    event.dispatchEvent(‘load’, ‘load事件触发’)
    // 移除load2事件
    event.removeEventListener(‘load’, load2)
    // 移除所有load事件
    event.removeEventListener(‘load’)
    4】Vue2.0中的发布订阅模式

    vue中的数据响应式其实是个发布订阅模式:
    数据劫持中,get函数是触发监听,把data()里面的每一个属性都绑定上了一个Watcher
    set函数是发布者,因为只要数据已更新,就会触发set函数,set函数就会告诉Dep让其执行notify,相当于this.$emit(‘notify’)。
    Dep是调度中心,负责收集Watcher依赖和收到发布者的命令执行notify方法,调度中心通知Watcher执行更新方法。Watcher去调用自己的update方法,经历Diff算法,最终执行render()去更新DOM。
    get函数是订阅者,用于订阅Watcher。

    21、JS正则表达式的使用
    1】语法
    可以使用以下两种方法之一构建一个正则表达式:
    使用一个正则表达式字面量,其由包含在斜杠之间的模式组成,如下所示:
    var re = /ab+c/;

    使用正则表达式字面量为正则表达式提供了脚本加载后的编译。当正则表达式保持不变时,使用此方法可获得更好的性能。
    或者调用RegExp对象的构造函数,如下所示:
    var re = new RegExp(“ab+c”);

    2】元字符

    3】分组符([],(),{})

    4】修饰符(i,g,m)

    5】量词(*,?,+,-,{n,m},?=n,?!=n)

    6】使用字符串方法
    在 JavaScript 中,正则表达式通常用于两个字符串方法 : search() 和 replace()
    search() 方法 用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串,并返回子串的起始位置。
    replace() 方法 用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
    7】例子
    需求一:在一句话英文中匹配一个hi的单词。
    var str = ‘Hi RegExp I love you so much Hi Hi hi’;
    var reg = new RegExp(“\bhi\b”,“gi”);//g是一个修饰符,表示全局匹配。\b是一个元字符,代表单词边界,匹配单词的开头和结尾。
    //直接量语法:
    reg2 = /\bhi\b/gi;
    console.log(str.match(reg2));//[‘hi’,‘hi’,‘hi’,‘hi’]
    //升级
    //匹配hi后面不远处跟着一个lucy
    var strlc = /\bhi\b.*\blucy\b/;
    var luch = ‘hi welcome to beijing lucy!!!’;
    console.log(luch.match(strlc));

    需求二:在一句话英文中匹配一个hi的单词。
    var reg = /0\d\d\d-\d\d\d\d\d\d\d\d/;//\d代表一个数字,等价于[0-9],\D匹配一个非数字字符,等价于[^0-9]
    var tel = “0123-887523146”;
    console.log(tel.match(reg));//0123-88752314;
    //这个连续写多次的方法很笨哎,所以引进了变量。
    regTel = /0\d{3}-\d{8}/;
    console.log(tel.match(regTel));//0123-88752314;

    需求三:写一个清除字符串前后空格的正则表达式。
    String.prototype.trim = function(){return this.replace(/(^\s*)|(\s*$)/g, “”);}
    var str2 = " hi space "//这里前后共有两个空格
    console.log(str2.length);//14
    console.log(str2.trim().length);//8
    console.log(str2.trim());//hi space

    需求四:匹配一个邮箱。
    var eReg = /\S*@\S*.\S*/;
    console.log(eReg.test(‘873619879@qq.com’))//true

    22、js 数组和类数组的区别
    1】类数组定义
    1)拥有length属性,其它属性(索引)为非负整数(对象中的索引会被当做字符串来处理);
    2)不具有数组所具有的方法;
      javascript中常见的类数组有 arguments对象和 DOM方法的返回结果。比如 document.getElementsByTagName()。
    2】判断是否是类数组
    function isLikeArray(o) {
    if (typeof o === ‘object’ && isFinite(o.length) && o.length >= 0 && o.length < 4294967296){
    // 4294967296: 2^32
    return true
    } else {
    return false
    }
    }
    3】类数组转换为数组
    1)Array.prototype.slice表示数组的原型中的slice方法
    注意这个slice方法返回的是一个Array类型的对象
    //slice的内部实现
    Array.prototype.slice = function(start,end){
    var result = new Array();
    start = start || 0;
    end = end || this.length; //this指向调用的对象,当用了call后,能够改变this的指向,也就是指向传进来的对象,这是关键
    for(var i = start; i < end; i++){
    result.push(this[i]);
    }
    return result;
    }
    2)使用es6
    ES6 提供在数组中提供了Array.from 把类数组转化成数组
    Array.from(‘一二三四五六七’) // [“一”, “二”, “三”, “四”, “五”, “六”, “七”] // 等效的es5是’一二三四五六七’.split(‘’)

    23、js网络请求中的防抖与节流
    防抖和节流,都是防止在一个时间段内,过于频繁去触发某个函数而采取的措施
    1】防抖:将多次执行变为最后一次执行
    思路:在第一次触发事件的时候,不是立即执行函数,而是给出一个delay时间值,例如500ms。
    如果在500ms内没有再次触发该事件,则执行函数;如果在200ms内有再次触发事件,则清除当前的计时器,重新开始计时器。
    实现:利用setTimeOut 和 clearTimeout来实现计时器的功能。

    2】节流:将多次执行变为每隔一段时间执行
    思路:短时间内大量触发同一事件,函数执行一次之后在某个指定的时间内不再执行,直到过了这个指定的时间才会重新生效
    实现:状态位 / 时间戳 / setTimeout标记

    3】区别
    防抖不会立刻执行,一般都要延迟一段时间才执行;节流则是立刻执行。
    4】应用场景
    抢购秒杀、监听鼠标滚轮事件:防抖
    列表页无限上拉加载:节流

    24、JS进制转换
    1)十进制转其他任意进制
    parseInt(str, radix)
    这个是把字符串(只能由字母和数字组成),这个只能是由低进制转高进制,如二进制转八进制,但是八进制不能转二进制,radix表示进制,取值2~36。
    将字符串str按照radix进制编码方式转换为10进制返回,没有radix,默认为10。
    2)其他任意进制转十进制
    parseInt(str, 10)

    25、JS中Number精度问题
    1】Number类型(64位的二进制)
    在js里,Number是一个双精度浮点数,用64位的二进制。
    1 符号位,0 表示正数,1 表示负数 s
    11 指数位(e)
    52 尾数,小数部分(即有效数字)

    2】0.1+0.2===0.3 吗?不相等,而是等于0.30000000000000004
    0.1 和 0.2 转换成二进制进行对阶运算的时候,尾数会发生无限循环,然后JS引擎对二进制进行截断之后,就造成精度丢失。对于小数来说,它们的二进制尾数特别容易循环。
    题外话:不仅仅是JS,Java 和 Python 在进行0.1+0.2的运算之后也不等于0.3。

    3】最大安全数(2的53次方)
    最大安全数字是 Math.pow(2, 53) - 1。限制了只有在2(-53) ~ 2(53)之间才能准确表示出来,大数超出位数直接截掉了。
    结论:只要不超过最大安全数,整数之间的运算,几乎不会出现精度问题;出精度问题的基本是小数之间的运算。

    4】处理方法
    1)对精度要求不高
    可以在运算之后进行 toFixed(n),toFixed可以进行四舍五入的操作。
    *注:Math.round也可以进行四舍五入的操作。
    2)精度要求很高的解决方案
    a、整数
    方案1:两个相加的整数,先转成字符串,然后长度要对齐,不够的话,往前面补0,再然后从个位开始相加。
    方案2:用ES10的bigInt来表示。用BigInt来处理时不会受到Number中的安全值范围的限制,所以不用担心精度丢失。
    b、小数
    将该小数放大为整数(乘),进行算术运算,再缩小为小数(除)。

  • 相关阅读:
    k8s中,“deployment”充当什么角色?有什么功能?
    扩散模型新应用——微软推出蛋白质生成框架EvoDiff
    Sql Server数据库附加数据库失败警告方法,有关详细信息,请单击“消息”列中的超链接(Win11)
    Web前端一套全部清晰 ① 学习路线
    五笔字根
    从零备战蓝桥杯——动态规划(背包dp篇)
    通过HTTP来总结网络编程知识
    【SpringCloud微服务实战07】Sentinel 服务保护
    现货白银交易时间笔记
    100道护网面试题大全(附答案)
  • 原文地址:https://blog.csdn.net/qq_37546835/article/details/125476736