目录
6、基本包装类型(string、number、boolean)
8、JS中的四大继承方案:继承就是子类继承父类中的属性和方法。
11、闭包:指有权访问另一个函数作用域中的变量的函数(JavaScript高级程序设计)
1、js调用栈
调用栈:管理执行上下文的栈称为执行上下文栈,又称调用栈。
当一段代码被执行时,JavaScript引擎先会对其进行编译,并创建执行上下文。执行上下文中包含了变量环境、词法环境、外部环境、this。
调用栈是有大小的,当入栈的执行上下文超过一定数目,JavaScript引擎就会报错,把这种错误叫做栈溢出。
支持隐式类型转换的语言称为弱类型语言,不支持隐式类型转换的语言称为强类型语言。
脚本语言、解释性语言、弱类型语言、基于对象的语言(模拟面向对象,模拟继承,因为继承是类与类的关系,面向对象的继承为多态服务的)、动态类型语言。
点语法中的属性名,点什么就是什么,等价于字符串,必须遵循命名规范。
2、JS九种数据类型及区别
- 基本数据类型(值类型):Number,String,Boolean,Undefined,symbol,Null,Bigint(表示任意精度的整数)。
- 复杂(引用)数据类型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、变量计算
- 字符串拼接
const a = 100 + 10// 110、 const b = 100+’10’ //’10010’ const c = true + ‘10’//’true10’
- ==(先进行隐式转换,然后尝试是否相等):
100 ==’100’ //true、
0 == ‘ ’ //true、
0==false //true、
false == ‘ ’//true、
null ==undefined //true
- 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//会报错
闭包两种主要形式:
- 函数作为返回值,返回之后再执行:实现了在全局变量下获取到局部变量中的变量的值。
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(没有其他对象引用过该对象),或对该对象的唯一引用是循环的,那么该对象占用的内存就会被回收。
- 什么是垃圾:没有被引用的对象就是垃圾,就要被清除。有个例外:如果几个对象引用形成一个环,互相引用,但根本访问不到它们,这几个对象也是垃圾,也要被清除。
- 如何检测垃圾:标记-清除算法。
- 全局变量被回收不了。
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节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
宏任务与微任务:
- 宏任务:setTimeout, setInterval, requestAnimation, I/O
- 微任务:prosess.nextTick,Promise,Object.observe,MutationObserver
- 先取出第一个宏任务,在执行下一个宏任务之前执行完所有的微任务。
主线程任务执行完毕后会把任务队列中的微任务全部执行,然后再执行一个宏任务,这个宏任务执行完再次检查队列内部的微任务,有就全部执行没有就再执行一个宏任务。
注意:事件循环是js实现异步的一种方法,也是js的执行机制。JS的执行和运行:js在不同的环境下执行方式不同,而运行环境指js解析引擎。