前端面试题整理【js篇】

目录

1、js调用栈

2、JS九种数据类型及区别

3、js检测数据类型四种办法

4、变量计算

5、JS深拷贝和浅拷贝

6、基本包装类型(string、number、boolean)

7、JavaScript原型,原型链?

8、JS中的四大继承方案:继承就是子类继承父类中的属性和方法。

10、作用域和作用域链

11、闭包:指有权访问另一个函数作用域中的变量的函数(JavaScript高级程序设计)

12、垃圾回收机制

13、哪些操作会造成内存泄漏?

14、new操作符具体干了什么?

15、谈谈This对象的理解 

16、js中callee和caller

17、bind、call、apply

18、如何删除数组成员。

19、手写一个简易的JQuery,考虑插件和扩展性。

20、正则

21、js Event Loop (事件循环)


1、js调用栈

调用栈:管理执行上下文的栈称为执行上下文栈,又称调用栈。

当一段代码被执行时,JavaScript引擎先会对其进行编译,并创建执行上下文。执行上下文中包含了变量环境、词法环境、外部环境、this。

调用栈是有大小的,当入栈的执行上下文超过一定数目,JavaScript引擎就会报错,把这种错误叫做栈溢出。

支持隐式类型转换的语言称为弱类型语言,不支持隐式类型转换的语言称为强类型语言

                          

脚本语言、解释性语言、弱类型语言、基于对象的语言(模拟面向对象,模拟继承,因为继承是类与类的关系,面向对象的继承为多态服务的)、动态类型语言。

点语法中的属性名,点什么就是什么,等价于字符串,必须遵循命名规范。

2、JS九种数据类型及区别

  1. 基本数据类型(值类型):Number,String,Boolean,Undefined,symbol,Null,Bigint(表示任意精度的整数)。
  2. 复杂(引用)数据类型Object,Array,Function。(null是特殊的引用类型,指针指向空地址)。

注:声明的变量但未初始化为undefined。未定义的变量是在程序中声明但未给出任何值的变量。调用函数时,如果没提供应提供的参数,该参数就等于undefined。当函数没有返回值时,默认返回undefined。如果尝试读取未定义变量的值,返回undefined。

区别:

  • Undefined、Null、Boolean、Number和 String,这5种基本数据类型可以直接访问,存放在栈(stack)中
  • 引用数据类型在栈内存中保存在堆内存中的引用地址,通过这个引用地址可以快速查找到保存到堆内存中的对象
  • symbol是基本数据类型(es6的新数据类型),定义唯一变量,通常用作Object的key。Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。但作为构造函数来说它并不完整,因为不支持语法:new Symbol()。
  • Symbol值作为属性名时,该属性是公有属性,可以在类的外部访问。但是不会出现在for...in、for...of的循环中,也不会被Object.keys()、 Object.getOwnPropertyNames()返回
  • 如果要读取到一个对象的Symbol属性,可以通过Object.getOwnPropertySymbols()和Reflect.ownKeys()取到。

let sy = Symbol("key1")

let syObject = {};

syObject[sy] = "kk";

console.log(syObject);

for (let i in syObject) {

  console.log(i);// 无输出  

Object.keys(syObject);                     // []

Object.getOwnPropertySymbols(syObject);    // [Symbol(key1)]

Reflect.ownKeys(syObject);                 // [Symbol(key1)]

3、js检测数据类型四种办法

(1)typeof:能识别除null外所有的值类型,能识别函数,不能判断null,obj,array

1 console.log(typeof "");//string

2 console.log(typeof 1);//number

3 console.log(typeof true);//boolean

4 console.log(typeof null);//object

5 console.log(typeof undefined)//undefined;

6 console.log(typeof []);//object

7 console.log(typeof function(){});//function

8 console.log(typeof {});//object

(2)instanceof原理:判断其原型链中是否找到该类型的原型只能用于判断引用数据类型(不准确)。A(实例对象) instanceof B(构造函数),可以判断这个实例是否由这个构造函数所创建,或者这个实例是否继承于这个父类。

function Foo(){}

var f1=new Foo();

console.log(f1 instanceof Foo);//true

console.log(f1 instanceof Object);//true

1 console.log("1" instanceof String);//false

2 console.log(1 instanceof Number);//false

3 console.log(true instanceof Boolean);//false

4 console.log([] instanceof Array);//true

5 console.log(function(){} instanceof Function);//true

6 console.log({} instanceof Object);//true

可以看到前三个都是以对象字面量创建的基本数据类型,但是却不是所属类的实例。如果通过new关键字去创建基本数据类型,会发现输出true,如下:

1 console.log(new Number(1) instanceof Number);//true

2 console.log(new String(“1”) instanceof String);//true

3 console.log(new Boolean(false) instanceof Boolean);//true

(3)constructor:判断一个对象的构造函数是不是类的构造函数。

1 console.log(("1").constructor === String);//true

2 console.log((1).constructor === Number);//true

3 console.log((true).constructor === Boolean);//true

6 console.log(([]).constructor === Array);//true

7 console.log((function() {}).constructor === Function);//true

8 console.log(({}).constructor === Object);//true

但若将构造函数的原型prototype随意指向Array的原型,constructor不能处理这种情况。

function Fn(){};

Fn.prototype=new Array();

var f=new Fn();

console.log(f.constructor===Fn);//false  f.constructor===Fn.prototype.constructor===Array

console.log(f.constructor===Array);//true

(4)(jQuery源码)Object.prototype.toString.call()最好用:所有的数据类型,都可以判断出来。

  var a = Object.prototype.toString;

 1 console.log(a.call("aaa"));

 2 console.log(a.call(1));

 3 console.log(a.call(true));

 4 console.log(a.call(null));

 5 console.log(a.call(undefined));

 6 console.log(a.call([]));

 7 console.log(a.call(function() {}));

8 console.log(a.call({}));

4、变量计算

  1. 字符串拼接
const a = 100 + 10// 110、

const b = 100+’10’ //’10010’

const c = true + ‘10’//’true10’
  1. ==(先进行隐式转换,然后尝试是否相等):

100 ==’100’ //true、

0 == ‘ ’ //true、

0==false //true、

false == ‘ ’//true、

null ==undefined //true

  1. If(){}中()是判断 truely变量还是falsely变量:

!!0===false 

 !!NaN===false   

!!’ ’===false

 !!null===false  

!!undefined===false

 !!false === false

(4)

 1 == true //true 

 2 == true //false(number和boolean/string用==比较时会把boolean转换为number再比较值) 

 0 == false //false

 0 ==’ ’ //true  

NaN == NaN //false

 [] == ![] //true  

 [] == [] //false

20+'28'-10 === 2018   100*null===0    100*undefined === NaN  100*’10’ === 1000

5、JS深拷贝和浅拷贝

浅拷贝和深拷贝都只针对于引用数据类型。

浅拷贝只复制指向某个对象第一层属性值的指针,而不复制对象本身,新旧对象还是共享同一块内存;

深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改变原对象;

区别:浅拷贝只复制对象的第一层属性、深拷贝可对对象的属性进行递归复制。

浅拷贝:把对象第一层属性或方法复制到另一个对象中。

法一:function extend(a,b) {

      for(var key in a){

        b[key]=a[key]  }  }

法二:Object.assign()实现浅拷贝或者说一层的深拷贝:

let obj2 = Object.assign({},obj1)

补:Object.assign(合并的对象,传入合并中的对象....)将多个对象{} 合并成一个独立对象。

深拷贝:在另一个对象中开辟空间,把对象中所有的属性或方法,进行递归复制。

法一:function extend(obj1, obj2){

    for(let key in obj1){

        let item = obj1[key]

        if(item instanceof Array){//不能用typeof item这不能分辨出Array、Object

            obj2[key] = []

            extend(item, obj2[key])

        }else if(item instanceof Object){

            obj2[key] = {}

            extend(item, obj2[key])

        }else{

            obj2[key] = item

        }

    }

}

缺陷:当遇到两个互相引用的对象,会出现死循环的情况,为了避免相互引用的对象导致死循环的情况,则应该在遍历的时候判断是否相互引用对象,如果是则退出循环。

法二:使用JSON.stringify()(把对象转成字符串),再JSON.parse()(把字符串转成新的对象)

缺陷:它会抛弃对象的constructor,不管这个对象原来的构造函数是什么,在深拷贝后都会变成Object;这种方法能正确处理的对象只有Number, String, Boolean, Array, 扁平对象,也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON;

function deepCopy(obj1){ //注:这种方法不能拷贝函数属性

    let _obj = JSON.stringify(obj1);

    let obj2 = JSON.parse(_obj);

    return obj2;

}

6、基本包装类型(string、number、boolean)

本身是基本数据类型(但有相应的属性和方法),在基本包装类型调用方法和属性的过程中,后台会自动执行以下步骤:

(1)自动创建String类型的一个实例(和基本类型的值不同,这个实例就是一个基本包装类型的对象)。

(2)调用实例上的方法。

(3)销毁这个实例对象str = null

引用类型和基本包装对象的区别在于:生存期。

引用类型所创建的对象,在执行期间一直在内存中,而基本包装对象只存在一瞬间。所以无法直接给基本包装类型添加方法,但可在基本包装对象的原型中添加。

7、JavaScript原型,原型链?

原型作用:共享属性和方法

当需要一个属性时,Javascript引擎会先看当前对象中是否有这个属性,如果没有,就会查找_proto_所指向构造函数中prototype原型对象是否有这个属性,如此递推下去,一直检索到Object对象,就形成了原型链的概念。

xialuo.__proto__--->Student.prototype,Student.prototype.__proto__--->People.prototype,People.prototype.__proto__--->Object.prototype,Object.prototype.__proto__--->null。

注:原型链最终的指向是Object的prototype, 而Object中的__proto__是null

prototype(函数的原型对象):函数才有prototype对象。

__proto__(实例对象的原型):对象才有__proto__(这里的对象除了狭义的对象,也包括函数、数组等对象)

原型链:对象的__proto__指向构造函数的prototype,构造函数的prototype本身也是个对象,是对象肯定也有__proto__,那他的__proto__指向了谁呢,顺着这个问题,延着一个对象__proto__向上查找,这条线路就是我们所说的原型链。

注:(1)通过构造函数,new出的对象,新对象的__proto__指向构造函数的prototype;

(2)所有普通函数的__proto__指向Function()的prototype;

(3)非构造函数new出的对象({} new Object() 对象的prototype)的__proto__指向Object的prototype。

(4)Object的prototype的__proto__指向null。

8、JS中的四大继承方案:继承就是子类继承父类中的属性和方法。

方案一:原型继承:类的一个实例,继承原型上的内容。

缺点:实例对象所继承父类的引用属性被共享;改变原型指向实现继承时,初始化了属性,所继承的属性一样,并且会丢失原本的原型对象,包括子类constroctor,需要加一个Student.prototyper.constroctor = Student。Student.prototype = new People()会在子类原型对象

上有父类的无用属性,可使用ES5,Student.prototype = Object.create(People.prototype)

优点:解决方法继承。

方案借用构造函数继承:在子类构造函数中借用父类构造函数,其执行时,方法中的this为子类实例。

缺点:只能继承父类的实例属性和方法,不能继承原型上属性和方法。

优点:继承的父类引用实例属性不会被共享,解决属性继承,并且值不重复。

方案三:组合继承:call继承+原型继承。

方案四:ES6中class类,extends继承和组合继承基本类似,在子类的constructor中须加上super()函数,相当于A.call(this)。

class People{

    constructor(name){//constructor内定义的方法和属性是实例对象自己的

        this.name = name

    }

    eat(){//constructor外定义的方法和属性是所有实例对象共享的

        console.log(`${this.name} eat something`)    

    }

}

class Student extends People{

    constructor(name, number){

        super(name)//在this之前一定要调用super()

        this.number = number

    }

    sayHi(){

        console.log(

            `姓名${this.name},学号${this.number}`

        )

    }

}

9、VO、AO、GO

VO变量对象variable object理解为代码编译时产生。

每个执行环境都有一个变量对象VO,绑定以下属性:1.函数形参。2.函数声明。3.变量声明。函数被调用后,执行环境就切换成对应的函数,此时活动对象就会产生。

AO(活动对象activation object)理解为函数执行时产生的。 进入函数执行环境后,AO就相当于函数的VO,只是在函数执行环境里VO属性不能被直接访问,需生成AO来替代访问。

GO全局预编译对象global object)

(1)生成GO对象GO{},这个GO就是window。

(2)将全局的变量声明储存到GO对象中,value为undefined。

(3)将全局函数名作为GO对象中的key,函数整体内容作为value。

10、作用域和作用域链

(1)作用域:变量和函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。

(2)作用域链:变量在定义这个变量的函数作用域中取值。但如果在当前作用域中没有查到值,就会向定义时的上级作用域去查,直到查到全局作用域,这个查找过程形成的链条就叫做作用域链。

自由变量查找规则:自由变量向上级作用域查找时,是在函数、变量定义的地方查找,不是在执行的地方查找。

JavaScript语言的作用域链是由词法作用域决定的,而词法作用域是由代码结构来确定的,由let\var\const声明的属于词法作用域。

//函数作为参数被传递

function print(fn){

    const a = 200

    fn()

}

const a = 100

function fn(){

    console.log(a)//变量在定义的地方取值,若没有,则向定义时的上级作用域中寻找

}

print(fn)//100

//函数作为返回值

function create(){

    const a = 100

    return function(){

        console.log(a)//未定义的变量是自由变量,自由变量在定义的地方寻找值,若没找到,则向变量定义时的上级作用域中寻找。

    }

}

const fn = create()

const a = 20

fn()//100

11、闭包:指有权访问另一个函数作用域中的变量的函数(JavaScript高级程序设计)

函数A内部有一个函数 B,函数B可以访问到函数 A 的变量,B在A的作用域之外执行,那么函数 B 就是闭包,B叫做闭包函数。一个函数总是可以访问其外部函数的参数和变量,即使外部函数被终结后。

当函数嵌套时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局作用域下可访问时,就形成了闭包

特性:a、函数嵌套函数。b、参数和变量不会在函数调用后被垃圾回收机制回收。c、在函数内部可以引用函数外部的变量。

优点:a、可以设计私有的方法和变量。b、保护函数内的变量安全,避免全局变量的污染。c、在内存中维持一个变量,作为缓存。

缺点:被引用的函数内的私有变量不能被销毁,增大了内存消耗,造成内存泄漏。解决方法是可以在使用完变量后手动为它赋值为null;

应用:(1)隐藏数据,做一个简单的cache工具。(2)函数节流、防抖

//闭包隐藏数据,只向外提供API

function createCache(){

    const data = {} //闭包中的数据被隐藏,不会被外界访问

    return {

        set: function(key, val){

            data[key] = val

        },

        get: function(key){

            return data[key]

        }

    }

}

const c = createCache()

c.set('a', 100)

console.log(c.get('a')) //100

data.b = 200//会报错

闭包两种主要形式:

  1. 函数作为返回值,返回之后再执行:实现了在全局变量下获取到局部变量中的变量的值。

function a(){

    var name = 'dov'

    return function(){

        return name

    }

}

var b = a()

console.log(b())//dov

function fn(){

    var num = 3

    return function(){

        var n = 0

        console.log(++n)

        console.log(++num)

    }

}

var fn1 = fn()

fn1() //1 4

fn1() //1 5

一般情况下,在函数fn执行完后,就应该连同它里面的变量一同被销毁,但是在这个

例子中,匿名函数作为fn的返回值被赋值给了fn1,这时候相当于fn1=function(){var n = 0 ... },并且匿名函数内部引用着fn里的变量num,所以变量num无法被销毁,而变量n是每次被调用时新创建的,所以每次fn1执行完后它就把属于自己的变量连同自己一起销毁,于是乎最后就剩下孤零零的num,于是这里就产生了内存消耗的问题。

(2)函数作为参数传递。

function f1(){

var n=999;

nAdd=function(){n+=1}

function f2(){

alert(n);

}

return f2;

}

var result=f1();

result(); // 999

nAdd();

result(); // 1000

result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么?原因在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

例题:实现命名空间函数:要求只有一个namespace函数,该函数可以存储内容也可以读取内容。得用闭包的形式,存储obj,因为namespace是全局变量不会被gc清理掉,而namespace依赖obj,则obj也不会被清理。

一个函数多种用途叫做重载。

这样设置的会被覆盖

Q:工作中如何使用闭包,和闭包react hooks有没有联系?

A:要理解闭包,首先理解javascript变量作用域,分两种:全局变量和局部变量。javascript语言的特殊处就是函数内部可以读取外部作用域中的变量。有时候需要得到函数内的局部变量,但是在正常情况下,这是不能读取到的,这时候就需要用到闭包。可以把闭包简单理解成“定义在一个函数内部的函数”。闭包是指有权访问另一个函数作用域中的变量的函数。其本质是函数的作用域链中保存着外部函数变量对象的引用。  本质上就是词法作用域(定义闭包的外部作用域)+函数可以作为值传递,

12、垃圾回收机制

定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为0(没有其他对象引用过该对象),或对该对象的唯一引用是循环的,那么该对象占用的内存就会被回收。

  1. 什么是垃圾:没有被引用的对象就是垃圾,就要被清除。有个例外:如果几个对象引用形成一个环,互相引用,但根本访问不到它们,这几个对象也是垃圾,也要被清除。
  2. 如何检测垃圾:标记-清除算法。
  3. 全局变量被回收不了。

13、哪些操作会造成内存泄漏?

内存泄漏指不再拥有或需要任何对象、属性之后,它们仍然存在于内存中。

发生内存泄漏:a、setTimeout第一个参数使用字符串而非函数。b、闭包、控制台日志、循环(两个对象彼此引用,且彼此保留时,就会产生一个循环)也会发生内存泄漏。

如何判断JavaScript中内存泄漏

使用chrome的Performance面板,观察内存变化。如果多次垃圾回收后,整体趋势向上,就存在内部泄漏的可能。

14、new操作符具体干了什么?

(1)创建一个空对象。

(2)将新对象的__proto__属性指向构造函数的ptototype属性。

(3)将构造函数的this指向该对象,并执行构造函数将属性和方法加入到this引用的对象中。

(4)新创建的对象由this所引用,并且最后隐式的返回this。

注:构造函数返回this对象,没有影响;构造函数返回值类型,没有影响;构造函数返回null,没有影响;构造函数返回的是其他对象,则实例化对象被替换成该对象。

const Person = function(name,age) {

    this.name = name

    this.age = age

}

function myNew(fn,name,age) {

    const obj = {} //创建一个空对象

    obj.__proto__ = fn.prototype //将该对象的`__proto__`属性指向构造函数的`prototype`

    const result = fn.call(obj,name,age) // 将构造函数的this指向该对象并执行构造函数

    return typeof result === 'object' ? result : obj // 返回

}

const person = myNew(Person,'Jack',18)

15、谈谈This对象的理解 

(方法或函数中的this为调用该方法或函数中的对象,若没有调用者,则是window)。

(1)this也叫调用上下文,构造函数中、原型方法中、对象方法中this都是实例对象,普通函数、定时器方法中和回调函数中this指window。

(2)this是在函数执行的时候才能确定指向谁,指向的是调用它的对象,this的指向是不断变化的。(es6箭头函数指向取决于其声明定义(书写)的位置,也就是编译时绑定,this永远指向上级作用域的this)

const obj = {

  sayThis: () => {

    console.log(this);

  }

}

obj.sayThis(); // window 因为JavaScript没有块作用域,所以this指向上层作用域,也就是最外层作用域window上。

const globalSay = obj.sayThis;

globalSay(); // window 浏览器中的 global 对象

(3)4种调用方式:方法调用模式(隐式绑定)、函数调用模式(默认绑定)、构造器调用模式(构造函数绑定)、apply/call调用模式(硬绑定)。

(4)改变this指针的方法:箭头函数、保存this指针引用,call,apply,bind。

16、js中callee和caller

caller:返回一个关于函数的引用,该函数调用了当前函数。

callee:返回正在执行的函数,也就是返回指定的function对象的正文。

17、bind、call、apply

bind 的作用与call和apply相同,区别是call和apply是立即调用函数,而bind是返回一个函数,需要调用的时候再执行。一个简单的bind函数实现如下:

Function.prototype.bind = function(ctx) {

var fn = this;

return function() {

fn.apply(ctx, arguments);

}

}

bind: var c = b.bind(a) // 绑定作用域对象

c(1,3)

call、apply区别:

  • 函数名.call(作用域对象, 参数1,参数2),
  • 函数名.apply(作用域对象, [参数1,参数2])

//call的实现原理

Function.prototype.call = function(ctx) {

ctx = ctx || window

ctx.fn = this //给ctx对象添加fn这个属性,或者ctx[‘fn’],this就是借用的函数

let arg = [...arguments].slice(1)

let result = ctx.fn(arg.join(‘,’)) 

delete ctx.fn  //delete操作符用于删除对象中的某个属性,但不能删除变量、函数

return result

}

18、如何删除数组成员。

为了不改变后面成员的索引值用delete arr[index],为了删除所占位置使用a.splice(index, 1)。

19、手写一个简易的JQuery,考虑插件和扩展性。

class JQuery{

    constructor(selector){

        const result = document.querySelectorAll(selector)

        const length = result.length

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

            this[i] = result[i] //因为i是变量,就用[]的方式。 this个类数组

        }

        this.length = length

        this.selector = selector

    }

    get(index){

        return this[index]

    }

    each(fn){

        for(let i = 0; i < this.length;i++){

            const element = this[i]

            fn(element)

        }

    }

    on(type, fn){

        return this.each(element => {

            element.addEventListener(type, fn, false)

        })

    }

}

//插件扩展性

JQuery.prototype.dialog = function(info){

    alert(info)

}

//“造轮子”

class myJQuery extends JQuery{

    constructor(selector){

        super(selector)

    }

    //扩展自己的方法

    addClass(className){   }

    style(data){       }

}

const $p = new JQuery('p')

$p.get(1)

$p.each(element => console.log(element.nodeName))

$p.on('click', () => alert('clicked'))

20、正则

*   . 除了\n以外的任意一个单个字符

    *    []  范围

    *    () 分组,提升优先级

    *    | 或者

    *    * 0-多次

    *    + 1-多次

    *    ? 0-1次

    *    {0,} 和*一样

    *    {1,} 和+

    *    {0,1} 和?

    *

    *    \d 数字中的一个

    *    \D 非数字

    *    \s 空白符

    *    \S 非空白符

    *     \W  特殊符号     #$*%

    *     \w 非特殊符号    _是非特殊符号

    *     ^ 取反,以什么开始

*     $ 以什么结束

//正则表达式中:g 表示的是全局模式匹配

//正则表达式中:i 表示的是忽略大小写

 //调用方法验证字符串是否匹配

var reg=/\d{1,5}/;

var flag=reg.test("小苏的幸运数字:888");  //不是str.test(reg)

var reg = /^[0-9a-zA-Z_.-]+[@][0-9a-zA-Z_.-]+([.][a-zA-Z]+){1,2}$/  // 邮箱

var str="中国移动:10086,中国联通:10010,中国电信:10000";

//把里面所有的数字全部显示出来,然后放在array中。

var array=str.match(/\d{5}/g);

   

var str = "  哦买噶的    ,太幸福了  ";

str = str.replace(/\s+/g, "");//空白符替换为空字符串

//所有的h(部分大小写)都替换成S

var str="HhpphH";

str=str.replace(/[h]/gi,"S"); //SSppSS

function styleHyphenFormat(propertyName) {

  function upperToHyphenLower(match) {

console.log(match)  //  L  W

    return '-' + match.toLowerCase();

  }

  return propertyName.replace(/[A-Z]/g, upperToHyphenLower);

}

var styleFormat=styleHyphenFormat('borderLeftWidth')//styleFormat的结果"border-left-width"

笔试题:

var format = (tpl, arr) => tpl.replace(/{{\$(\d+)}}/ig, (match, p1) => arr[p1] || '')

console.log(format('<div>{{$0}},{{$1}}<span>{{$2}}</span></div>', ['小明0号', '小明1号', '小明2号']))

 正则表达式字符串的方法:

(1)split():可以将一个字符串,拆分为一个数组;方法中可以传递一个字符串作为参数,这个方法将会根据正则表达式来拆分字符串;这个方法即使不指定全局匹配,也会全部拆分。

    var str = "1a2b3c4d";

    var result = str.split(/[A-z]/);

console.log(result);  //["1", "2", "3", "4", ""]

(2)search():可以搜索字符串中是否含有指定的内容;如果搜索到指定内容,则会返回第1次出现的索引,如果没有搜索到返回-1;它可以接受一个正则表达式作为参数,然后会根据正则表达式去检索字符串;search()只会查找第一个,即使设置全局匹配也没用

       var str = "hello abc hello aec";

       //搜索一个字符串中是否含有abc或aec或afc

       var result = str.search(/a[bef]c/);

       console.log(result);

(3)match():可以根据正则表达式,从一个字符串中将符合条件的内容提取出来;默认情况下match只会找到第一个符和要求的内容,找到以后就停止检索;可以设置正则表达式为全局匹配模式,这样就可以匹配到所有内容;可以为一个正则表达式设置多个匹配模式,且顺序无所谓;match()会将匹配到的内容封装到一个数组中返回,即使查询到一个,也是数组。

   var str = "1a2b3c4d5e";

   var result = str.match(/[A-z]/g);

//   var result = str.match(/[a-z]/ig);

   console.log(result);

//   console.log(Array.isArray(result));    判断结果是否为数组

(4)replace():可以将字符串中指定的内容替换为新的内容。参数:1、被替换的内容,可以接受正则表达式作为参数。2、新的内容。

    var str = "1a2b3c4d5e";

//    var result = str.replace("a","@-@");

    var result = str.replace(/[a-z]/ig,""); //将字母删掉

    console.log(result);

function fn(str){

    let arr = str.split(/[,]|[,]/)

    let res = []

    for(let i = 0; i < arr.length; i ++){

        let item = arr[i].replace(/\s/g, '')

        if(/^[a-z]+$/.test(item)){

            res.push(item)

        }

    }

    return res

}

let str = 'beijing,Shanghai,guangzh ou , , 123,金邦'

console.log(fn(str))  //[ 'beijing', 'guangzhou' ]

21、js Event Loop (事件循环)

事件循环:

JS是单线程,有一个主线程和调用栈,所有任务被放到调用栈中等待主线程:

a:同步任务进入主线程,异步任务进入Event Table并注册函数。等异步任务有了结果,会把回调函数移入事件队列中。

b、主线程任务执行完毕后,调用栈被清空,主线程会去任务队列读取已注册的异步任务的回调函数,进入主线程执行。

上述过程会不断重复,也就是常说的Event Loop(事件循环)。js引擎存在监视进程,会持续不断的检查主线程执行栈是否为空。

简而言之:主线程执行的时候,遇到异步任务,会将异步任务放在事件队列中。遇到同步任务会将其放入到主线程中执行。当主线程执行完毕后,会执行事件队列中异步任务。

:js单线程原因:防止DOM树修改冲突。JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

宏任务与微任务:

  1. 宏任务:setTimeout, setInterval, requestAnimation, I/O
  2. 微任务:prosess.nextTick,Promise,Object.observe,MutationObserver
  3. 先取出第一个宏任务,在执行下一个宏任务之前执行完所有的微任务。

主线程任务执行完毕后会把任务队列中的微任务全部执行,然后再执行一个宏任务,这个宏任务执行完再次检查队列内部的微任务,有就全部执行没有就再执行一个宏任务。

注意:事件循环是js实现异步的一种方法,也是js的执行机制。JS的执行和运行:js在不同的环境下执行方式不同,而运行环境指js解析引擎。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值