3.利用hash技术解决以上提到的Ajax的两个缺陷比较成熟的应用举例
Gmail可以说是把Ajax技术用到了极致,Google采用hash来解决Ajax技术的两大缺陷。
又试了下Edge 版本 25. 也是一样的结果
之前隐约听说各浏览器在处理console.log的时候有很大的不同,试着在这个方向上考虑一下吧
js作为脚本语言运行在浏览器中,浏览器就是js的运行环境。对于众多风云的浏览器厂商来说, 他们的内核又是不一样的。浏览器内核分为两种:渲染引擎和js引擎。
渲染引擎:负责网页内容呈现的。
Js引擎:解释js脚本,实现js交互效果的。
首先我们js文件以scirpt标签元素呈现在html里面的。浏览器根据html文件以此解析标签,当解 析到scirpt标签时,会停止html解析,阻塞住,开始下载js文件并且执行它,在执行的过程中,如 果是第一个js文件此时浏览器会触发首次渲染(至于为什么,自己做下实验,不懂的可以留言)。 所以出现一个问题js文件大大阻碍了html页面解析及渲染,所以引入async和defer两个属性(对于 首屏优化有很大的提升,也要谨慎使用)
async:开启另外一个线程下载js文件,下载完成,立马执行。(此时才发生阻塞)
defer:开启另一个线程下载js文件,直到页面加载完成时才执行。(根本不阻塞)
string:由多个16位Unicode字符组成的字符序列,有单引号或双引号表示
number:采用了IEEE754格式来表示整数和浮点数值
null:只有一个值的数据类型,值为null.表示一个空对象指针,但用typeof操作会返回一个对象。一般 我们把将来用于保存对象的变量初始化为null.
undefined:这个类型只有一个值,在声明变量未进行赋值时,这个变量的值就是undefined.
object:就是一组数据和功能的集合,无序的键值对的方式存储。可以通过new操作符和创建对象构造函数 来创建。常见的对象类型有array,date,function等.
0.1和0.2在转换成二进制后会无限循环,由于标准位数的限制后面多余的位数会被截掉,此时就已经出现 了精度的损失,相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成 0.00004。
只要在当前实例的原型链上,用instanceof检测出来的结果都是true,所以在类的原型继承中,最后检测 出来的结果未必是正确的.而且instanceof后面必须更一个对象。
每个构造函数的原型对象都有一个constructor属性,并且指向构造函数本身,由于我们可以手动修改 这个属性,所以结果也不是很准确。 不能检测null和undefined
调用Object原型上的toString()方法,并且通过call改变this指向。返回的是字符串
javaScript作为一门弱类型语言,本质为一个变量可以被赋予不同的数据类型。代码简洁灵活,但稍有 不慎,会出现很多坑。
javaScript也作为一门动态类型语言,在运行时,可以随便改变其变量的结构。
所以js变量可以做任意的类型转换,有两种方式,显示类型转换和隐士类型转换。
当引用类型转换时,就稍微有些复杂,我们来举个例子:(所有对象转换boolean都为true)
当对象进行类型转换时:
1.首先调用valueOf,如果执行结果是原始值,返回,如果不是下一步
2.其次调用toString,如果执行结果是原始值,返回,如果不是,报错。
当使用显示类型转换成String时,执行顺序则是先调用toString,其次调用valueOf
一错就知道,一做又全忘哈哈哈哈。
1.for in:自身和继承属性,可枚举,不含Symbol
javascript采用的静态作用域,也可以称为词法作用域,意思是说作用域是在定义的时候就创建了, 而不是运行的时候。此话对于初学者很不好理解,看看下面这个例子:
是不是非常违背常理啊,你看嘛,aa在bb里面调用的,aa函数里面没有a变量,那么就应该去调用它的作 用域里找,刚好找到a等于2。思路是完美的,可是js的作者采用的静态作用域,不管你们怎么运行,你们 定义的时候作用域已经生成了。
变量和函数能被有效访问的区域或者集合。作用域决定了代码块之间的资源可访问性。
作用域也就是一个独立的空间,用于保护变量防止泄露,也起到隔离作用。每个作用域里的变量可以相同命名,互不干涉。就像一栋房子一样,每家每户都是独立的,就是作用域。
作用域又分为全局作用域和函数作用域,块级作用域。 全局作用域任何地方都可以访问到,如window,Math等全局对象。 函数作用域就是函数内部的变量和方法,函数外部是无法访问到的。 块级作用域指变量声明的代码段外是不可访问的,如let,const.
知道作用域后,我们来说说什么是作用域链?
表示一个作用域可以访问到变量的一个集合。函数作为一个对象有一个[[scope]]属性,就是表示这个集合的。再来理解几个概念词:
执行上下文:代码运行的环境,分为全局上下文和函数上下文。
举例子来说明一下:(借用的例子)
第一步: a 函数定义
我们可以从上图中看到,a 函数在被定义时,a函数对象的属性[[scope]]作用域指向他的作用域链scope chain,此时它的作用域链的第一项指向了GO(Global Object)全局对象,我们看到全局对象上此时有5个属性,分别是this、window、document、a、glob。
第二步: a 函数执行
第三步: b 函数定义
第四步: b 函数执行
Object),GO对象里依然有5个属性,分别是this、window、document、a、golb。 以上就是上面代码执行完之后的结果。
不会闭包的程序员不是好程序员。
mdn:闭包是指那些能够访问自由变量的函数。
维基百科:在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性.
我:一个作用域可以访问另一个作用域的变量,就产生闭包。之前比喻作用域就好比一栋房子每一户,闭包相当于串门。
自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
闭包=函数+函数能够访问的自由变量。
从技术的角度讲,所有的JavaScript函数都是闭包。
foo 函数可以访问变量 a,但是 a 既不是 foo 函数的局部变量,也不是 foo 函数的参数,所以 a 就是自由变量。
那么,函数 foo + foo 函数访问的自由变量 a 不就是构成了一个闭包嘛……
我们在看看ECMAScript中,闭包指的是:
1.从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
2.从实践角度:以下函数才算是闭包:
· 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
· 在代码中引用了自由变量
下面我们开始实践论证:(例子依然是来自《JavaScript权威指南》)
因为变量查找的规则是通过作用域链的,作用域链是在函数定义的时候就已经确定了, 所以我们来看看定义f函数时候的[[scope]]属性:
当执行到 data[0] 函数之前,此时全局上下文的 VO 为:
当执行 data[0] 函数的时候,data[0] 函数的作用域链为:
不多说,要会背下面这张图,理解起来很简单的。
javascript万物皆对象,每个对象都有一个__proto__属性,指向了创造它的构造函数的原型对象。
每个函数都有一个原型对象,prototype,当使用new 创造对象时继承这个对象。
下面就有问题了,谁创造了A这个构造函数呢,还有谁创造了A.prototype这个对象呢?
所有函数都是由Function创建的
刚说了所有函数都是由Function创建的,也包括自己。也就是说Function创造了自己:
Object刚讲的是顶级函数,所以也是函数:(所有的鱼都归猫管哈哈哈哈哈)
所有的对象都是由Object构造函数创建的:
那么Object.prototype也是对象啊,是由谁创建的呢,记住万物皆空,何尝不是人生,到头来什么都会没有。
1.在访问对象的某个成员的时候会先在对象中找是否存在
2.如果当前对象中没有就在构造函数的原型对象中找
3.如果原型对象中没有找到就到原型对象的原型上找
4.直到Object的原型对象的原型是null为止
这个问题直接从结果入手,this指向一共有七种情况,下面一 一说起。
new出来的对象,this指向了即将new出来的对象。
当做普通函数执行,this指向window。
作为对象方法,this指向了这个对象。(新对象绑定到函数调用的this)
一旦有变量直接指向了这个方法,this为window.
如果在方法里面执行函数,this指向window.
原型定义方法的this指向了实例对象。毕竟是通过对象调用的。
this指向传入的对象。
在方法中定义函数应该是指向window,但是箭头函数没有自己的this,所以指向上一层作用域中的this.
谁调用方法,this指向谁。
缺点:多个实例对引用类型操作会被篡改
· 只能继承父类的实例属性和方法,不能继承原型属性/方法。
· 性能不好,每个子类都会拥有父类实例的副本。
就是将上两种方法结合起来
利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。
· 共享引用类型属性的值
· 无法传递参数 缺点:
在原型式继承的基础上,增强对象,返回构造函数.
借用阮一峰老师的es6中extends继承,眼过千遍,不如手写一遍。复制代码
上面的只能说去应付面试,这个才是我们开发中最常用的,所以必须掌握。
上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法
Object.getPrototypeOf可以使用这个方法判断,一个类是否继承了另一个类
上面代码中,子类B的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。
上面代码中,new.target指向当前正在执行的函数。可以看到,在super()执行时,它指向的是子类B的构造函数,而不是父类A的构造函数。也就是说,super()内部的this指向的是B。
作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。
上面代码中,super()用在B类的m方法之中,就会造成语法错误。
第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
这里需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。
如果属性定义在父类的原型对象上,super就可以取到。
上面代码中,属性x是定义在A.prototype上面的,所以super.x可以取到它的值。
ES6 规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。
由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。
如果super作为对象,用在静态方法之中,这时super将指向父类,而不是父类的原型对象。
上面代码中,super在静态方法之中指向父类,在普通方法之中指向父类的原型对象。
另外,在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。
在JavaScript中,每一个变量在内存中都需要一个空间来存储。内存空间又分为栈内存和堆内存。 基本数据类型保存在栈中,引用类型保存于堆中。
· 首先创建变量a,在创建字符串'a',使变量a指向'a',a保存的是这个值
· 接着又创建字符串'b',使变量a指向字符串'b',同时删除'a'
· 深拷贝了一份a(重新创建了'b'),使变量b指向了'b',变量a和变量b互不受影响。
· 创建一个对象,开辟了一个堆内存,使变量obj1指向这个对象(堆内存)的地址
· 创建一个变量obj2,将obj1的值赋值给obj2,就是将地址赋值给了obj2,这时obj1和obj2指向的是同一个堆内存
· 两个变量就相互影响。
所有函数的参数都是按值传递的。也就是说,函数外部的值。复制给函数内部的参数.
分析:之前说过,函数参数是按值传递的。所以在函数内部,obj这个局部变量保存的是person这个对象的地址。第一步操作了这个地址下的name属性为'node',第二步,就是将这个变量指向了一个新的堆地址,所以外部person对象丝毫不受影响,name属性依旧为node.
由于数据存储方式不同,对于引用数据而言,有了浅拷贝和深拷贝。浅拷贝是指拷贝地址,公用同一个堆内存,两个变量相互受影响,深拷贝使指,开辟一块内存空间,保存相同的值。互不受影响。
总结:输出结果发现,函数和undefine都不见了,正则变成一个空对象了。
总结:无法拷贝函数,正则,日期对象。
环就是对象循环引用自己。比如:
而使用上面两种方法拷贝就会直接报错的。所以我们就要借鉴WeakMap数据结构,每一次拷贝的时候就去先weakMap查询该对象是否已经被拷贝,如果已经拷贝取出该对象并返回,所以我们要改造一下clone函数:
结果:说明拷贝成功了,没有报错。
只剩下函数没有解决了。
高阶函数定义: 接收函数作为参数或者返回函数的函数。
所以我们来实现一下map:
定义:是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
再来看看之前的面试题:
如果参数是100个呢,难道要写100次吗,如果参数是未知的呢,所以这个写法没有通用性。
分析:首先我们做的要把所有参数收集起来,然后进行统一处理。
只有在对该函数取值时,才会进行类型转换,才会调用toString返回相加的值
否则只是调用则返回函数而不是相加值
ajax是一系列技术的统称,实现向服务器发送数据接受数据而页面不刷新。
XMLHttpRequest是浏览器提供的一个api,在js中,也就是一个构造函数。
简单完成一个ajax请求:
· 2:发送 -- 已经调用.send()方法,但尚未接收到响应;
· 3:接收 -- 已经接收到部分响应数据;
· 4:完成 -- 已经接收到全部响应数据,而且已经可以在客户端使用了;
ajax也可以取消事件监听,将异步改为同步,调用abort()
· loadstart:在接收到响应数据的第一个字节时触发
· progress:在接收响应期间持续不断地触发
· error:在请求发生错误时触发;
· load:在接收到完整的响应数据时触发;
· appendChild:向当前节点的子节点列表的末尾添加新的子节点
这个方法允许你将任何有效的 HTML 字符串插入到一个 DOM 元素的四个位置,这四个位置由方法的第一个参数指定,分别是:
· 'afterbegin': 元素内,位于现存的第一个子元素之前
· 'beforeend': 元素内,位于现存的最后一个子元素之后
· matches:以判断出一个元素是否匹配一个确定的选择器
· contains:可以检测出一个元素是否包含另一个元素
在处理用户交互的时候,当前页面的 DOM 元素通常会发生很多变化,而有些场景需要开发者们监听这些变化并在触发后执行相应的操作。MutationObserver 是浏览器提供的一个专门用来监听 DOM 变化的接口,它强大到几乎可以观测到一个元素的所有变化,可观测的对象包括:文本的改变、子节点的添加和移除和任何元素属性的变化。 如同往常一样,如果想构造任何一个对象,那就 new
复制代码传入构造函数的是一个回调函数,它会在被监听的 DOM 元素发生改变时执行,它的两个参数分别是:包含本次所有变更的列表 MutationRecords 和 observer 本身。其中,MutationRecords 的每一条都是一个变更记录,它是一个普通的对象,包含如下常用属性:
· attributeName: 值发生改变的属性名,如果不是属性变更,则返回 null
· nextSibling: 被添加或移除的子元素之后的兄弟节点
根据目前的信息,可以写一个 callback 函数了:
复制代码至此,我们有了一个 DOM 观察者 observer,也有了一个完整可用的 DOM 变化后的回调函数 callback,就差一个需要被观测的 DOM 元素了:
复制代码在上面的代码中,我们通过调用观察者对象的 observe 方法,对 id 为 target 的 DOM 元素进行了观测(第一个参数就是需要观测的目标元素),而第二个元素,我们传入了一个配置对象:开启对属性的观测 / 只观测 class 属性 / 属性变化时传递属性旧值 / 开启对子元素列表的观测。 配置对象支持如下字段:
· attributeOldValue: Boolean,当监听元素的属性发生变化时,是否记录并传递属性的上一个值
· characterData: Boolean,是否监听目标元素或子元素树中节点所包含的字符数据的变化
· subtree: Boolean,是否扩展监视范围到目标元素下的整个子树的所有元素
当不再监听目标元素的变化时,调用 observer 的 disconnect 方法即可,如果需要的话,可以先调用 observer 的 takeRecords 方法从 observer 的通知队列中删除所有待处理的通知,并将它们返回到一个由 MutationRecord 对象组成的数组当中:
BOM为浏览器对象模型,接下来一一来回顾。
所有 JavaScript 全局对象、函数以及变量均自动成为 window 对象的成员。
全局变量是 window 对象的属性。
全局函数是 window 对象的方法。
全局变量不能通过 delete 操作符删除;而 window 属性上定义的变量可以通过 delete 删除
访问未声明的变量会抛出错误,但是通过查询 window 对象,可以知道某个可能未声明的变量是否存在。
window.location 对象用于获得当前页面的地址 (URL),并把浏览器重定向到新的页面。
window.location.assign(url) : 加载 URL 指定的新的 HTML 文档。 就相当于一个链接,跳转到指定的url,当前页面会转为新页面内容,可以点击后退返回上一个页面。
window.location.replace(url) : 通过加载 URL 指定的文档来替换当前文档 ,这个方法是替换当前窗口页面,前后两个页面共用一个窗口,所以是没有后退返回上一页的
JS中有三个基本数据类型是比较特殊的存在,分别是String、number、Boolear,这个三个基本是由自己对应的包装对象。并且随时等候召唤。
其实就是对象,在对基本类型数据进行操作时,首先会创建一个相应的对象代替,操作完之后并删除。
引用类型和基本包装对象的区别在于,生存期,引用类型所创建的对象,在执行期间一直在内存中,而基本包装对象只是存在一瞬间(也就是执行完后变量就变成null)。
a是一个基本类型的数据,是不具备任何方法的,但是我们可以调用slice方法
这个时候就是被包装了一层对象。
居然是对象的话,我们可以添加属性
但是访问却访问不到,就像之前说的,包装对象在进行操作完就删除了,也就是说,a.name只存在了一瞬间。
多态就是通过对传递的参数判断来执行逻辑,即可实现一种多态处理机制。
封装就是把客观事物封装成抽象的类,隐藏属性和方法的实现细节,仅对外公开接口。也就是说,封装就是将属性和方法组成一个类的过程就称之为封装。
· 优点:代码简单易懂
· 缺点:创建多个对象会产生大量的代码,编写麻烦,且并没有实例与原型的概念
· 优点:避免创建大量对象时代码的臃肿
· 缺点: p1与p2之间没有内在联系
· 优点:实例化对象和构造函数之间存在关联
· 缺点:浪费内存,构造函数中定义的方法名action一样,但实例化出来的对象名不一样,造成一个内存的浪费
面试题:js异常能否被try catch扑捉到?复制代码
正确答案:只有在线程执行已经进入try catch未执行完的时候抛出来的的异常可以捕捉的到。 也就是说只能捕获同步js代码的错误。
代码报错的时候,线程执行未进入 try catch,那么无法捕捉异常。
比如语法异常(syntaxError),因为语法异常是在语法检查阶段就报错了,线程执行尚未进入 try catch 代码块,自然就无法捕获到异常。
setTimeout 里面报错,实际上是 100ms 之后执行的代码报错,此时代码块 try catch 已经执行完成,111 都已经被执行了,故无法捕捉异常。
js是运行在浏览器里面的,而浏览器是多进程多线程的。但是分配给Js,只有一个线程。因为Js可以操作dom,所以js和dom渲染一定是互斥。不然又是dom渲染,又是js操作dom,那么浏览器到底听谁的呢。多线程也会带来相应的复杂程度。
单线程特点是:任务依次执行,后面的必须等前面的任务完成后,才能执行下一个任务。
假如一个任务需要花费大量时间,那后面的任务都要等待,那这语言也太不行了。所以有了异步任务和回调函数。意思就是说,这个耗时的任务交给其他线程来执行,当任务执行完后,触发回调函数即可。所以就不阻碍js同步任务的执行。
现在又要一个问题,这些回掉函数是如何执行的,执行顺序又是如何?
浏览器维护了一个事件队列,当主线程任务执行完成后,每隔相应的时间就会来事件队列查看,看看有咩有需要执行的任务。任务又分为宏任务和微任务,宏任务快于微任务。
整体的执行顺序如上图所示:
· 检查是否有微任务,全部执行。(微任务产生的微任务也会全部执行),然后清空微任务队列
· 渲染 -开启下一个宏任务 说的多不如做的多:
· 1、将脚本任务放入到task队列。
· 5、清空microtask队列之后,进入下一个循环。
· 4、清空microtask队列之后,进入下一个循环。
· 4、清空microtask队列之后,进入下一个循环。
· 1、从task中在取出一个set3任务,运行的结果是输出timer3
希望我的文章对大家有帮助哦,剩下两篇文章一周之内也都会发布的,基本也完成的差不多了,希望大家多多支持。