JS
VO:变量对象
AO:活动对象
-
defer & async
常规
script
脚本浏览器会立即加载并执行,异步加载使用async
与defer
二者区别在于aysnc
为无序,defer
会异步根据脚本位置先后依次加载执行
Vue
生命周期
从开始创建、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。
- beforeCreate
- 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调
- created
- 实例已经创建完成,数据观测(data observer) 和 event/watcher 事件配置完成,但真实dom还没有生成,$el 还不可用。
- 调用数据,调用方法,调用异步函数
- beforeMount
- 相关的render函数(模板)首次被调用
- mounted
- 挂载完成, $el 可用,dom渲染完成
- beforeUpdate
- 数据更新时调用,发生在学你dom重新渲染和打补丁之前,可以在这个钩子中进一步地更改状态,并不会触发附加的重渲染过程。当我们更改Vue的数据,都会触发改钩子
- updated
- 组件dom 已经更新,可以执行依赖于dom的操作,然而应该避免在此更改状态,可能会导致更新无限循环
- beforeDestroy
- 实例销毁之前调用,在这一步,实例仍然完全可用
- destroyed
- 实例销毁后调用
Vue生命周期在真实场景下的业务应用
created: 进行ajax请求异步数据的获取、初始化数据
mounted: 挂载元素dom节点的获取
nextTick: 针对单一事件更新数据后立即操作dom
updated: 任何数据的更新,如果要做统一的业务逻辑处理
watch: 监听数据变化,并做相应的处理
插槽
-
缩写 v-slot:header => #header,#default="{user}"
-
说明 插槽就是子组件中提供给父组件使用的一个占位符,用 表示,父组件可以在这个占位符中填充任何模板代码,如HTML,组件等,填充的内容会替换子组件的标签
-
场景 在构建页面过程中一般会把用的比较多的公共的部分抽取出来作为一个单独的组件,但在实际开发过程中这个组件并不能完全满足需求,这个时候我们就需要用到插槽来分发内容
-
作用 可以拓展组件,去更好的复用组件和对其做定制化处理
-
编译作用域 父级 模板里所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的
-
注意:v-slot只能添加在
<template>
上 ,默认插槽除外<current-user v-slot:default="slotProps"> {{ slotProps.user.firstName }} </current-user> // 简写 <current-user v-slot="slotProps"> {{ slotProps.user.firstName }} </current-user> // 默认插槽的缩写语法不能和具名插槽混用,因为会导致作用域不明确 <!-- 无效,会导致警告 --> <current-user v-slot="slotProps"> {{ slotProps.user.firstName }} <template v-slot:other="otherSlotProps"> slotProps is NOT available here </template> </current-user> // 只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法: <current-user> <template v-slot:default="slotProps"> {{ slotProps.user.firstName }} </template> <template v-slot:other="otherSlotProps"> ... </template> </current-user>
-
分类
默认插槽
// parent // 无插槽内容 <submit-button></submit-button> // 有插槽内容 <submit-button>Save</submit-button>
// <submit-button> <button type="submit"> <slot>Submit</slot> // 如果父组件没有提供任何插槽内容,显示Submit </button> // 显示 <button type="submit"> <slot>Save</slot> // 显示Save </button>
具名插槽
// child <myslot> <template> <div class="container"> <header> <!-- 我们希望把页头放这里 --> </header> <main> <!-- 我们希望把主要内容放这里 --> </main> <footer> <!-- 我们希望把页脚放这里 --> </footer> </div> </template> // 修改后 <template> <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> </template>
// parent <template> <myslot> <div>大家好我是父组件</div> <template v-slot:header> <h1>Here might be a page title</h1> </template> <p>A paragraph for the main content.</p> <p>And another one.</p> <template v-slot:footer> <p>Here's footer info</p> </template> </myslot> </template> <script> import myslot from './myslot'; export default { components: { myslot } } </script>
作用域插槽(插槽内容能够访问子组件中才有的数据)
// child current-user <span> <slot v-bind:user="user"> {{ user.lastName }} </slot> </span> // parent <current-user> <template v-slot:default="slotProps"> {{ slotProps.user.firstName }} </template> </current-user> // 解构 <current-user> <template v-slot:default="{user}"> {{ user.firstName }} </template> </current-user> // 重命名 <current-user> <template v-slot:default="{user: person}"> {{ person.firstName }} </template> </current-user> // 自定义后备内容 <current-user> <template v-slot:default="{user={firstName: 'hui'}}"> {{ person.firstName }} </template> </current-user>
动态插槽名
<base-layout> <template v-slot:[dynamicSlotName]> ... </template> </base-layout>
插件
添加全局方法或者property。如:vue-custom-element
添加全局资源:指令/过滤器/过渡等。如:vue-touch
通过全局 mixin 来添加一些组件选项。如vue-router
添加全局实例方法,通过把它们添加到 config.globalProperties 上实现
一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router
使用插件
通过全局方法 Vue.use()
使用插件。它需要在你调用 new Vue()
启动应用之前完成,Vue.use
会自动阻止多次注册相同插件,届时即使多次调用也只会注册一次该插件。
3.0 新特性
- 组合式API,2.0为选项式API
对原data中的属性,使用ref包装为响应式对象,对原prop的属性,使用toRefs包装为响应式对象
- props
因为props
是响应式的,你不能使用 ES6 解构,因为它会消除 prop 的响应性。 - context => { attrs, slots, emit}:
context
是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对context
使用 ES6 解构。
- props
渲染机制和优化
虚拟 DOM 是轻量级的 JavaScript 对象,由渲染函数创建。它包含三个参数:元素,具有数据、prop、attr 等的对象,以及一个数组。数组是我们传递子级的地方,子级也具有所有这些参数,然后它们也可以具有子级,依此类推,直到我们构建完整的元素树为止。
Object, Set和Map的区别
-
key的不同,在Object 中, key必须是简单数据类型(整数,字符串或者是symbol),而在Map中则可以是JavaScript 支持的所有类型数据,也就是说可以用一个Object 或 Function 来当做一个Map元素的key
-
使用
Obejct
- object 可以通过Object.keys,Object.value,Object.entries,for...in...等遍历拿到键或者值
- 可以用过 "a" in obj 来判断obj中是否有a属性
Map
- Map可以通过this.keys,this.values,this.entries,for...of...等遍历拿到键或者值
- 常用方法map.set(key,value)、map.get(key)、map.has(key)
- Map 在存储大量元素的时候性能表现更好
- var map = new Map([[1, 2], [2, 3]]); // map = {1 => 2, 2 => 3}
Set
-
Set是一种叫做集合的数据结构,Map是一种叫做字典的数据结构
-
Set和Map主要的应用场景在于数组去重和数据存储
-
常用方法
// size:返回集合所包含元素的数量 // Set的方法: // 操作方法 add(value)//向集合添加一个新的项 delete(value)//从集合中移除一个值 has(value)//如果值在集合中存在,返回true,否则false clear()// 移除集合里所有的项 遍历方法 keys()//返回一个包含集合中所有键的数组 values()//返回一个包含集合中所有值的数组 entries//返回一个包含集合中所有键值对的数组(感觉没什么用就不实现了) forEach()//用于对集合成员执行某种操作,没有返回值
不同
-
JSON 直接支持 Object,但不支持 Map
-
Map 是纯粹的 hash, 而 Object 还存在一些其他内在逻辑,所以在执行 delete 的时候会有性能问题。所以写入删除密集的情况应该使用 Map。
-
Map 会按照插入顺序保持元素的顺序(FIFO 原则),而Object做不到
-
Map 自身有 size 属性,可以自己维持 size 的变化。Object 则需要借助
Object.keys()
来计算
console.log(Object.keys(obj).length);
- 如何确定一个类型是不是支持迭代呢? 可以使用以下方法:
console.log(typeof obj[Symbol.iterator]); // undefined
console.log(typeof map[Symbol.iterator]); // function
日志追踪实用技巧
-
try...catch...这种方式适合捕获可预见的错误,无法捕获异步错误
-
window.onerror 可以捕获到语法错误,但无法捕获到网络异常的错误。
-
window.addEventListener("error", fun, true) 折中方式虽然可以捕获到网络请求的异常,但是无法判断HTTP的状态是404,还是其它如500等,还需要配合服务端日志才进行排查分析才可以
-
unhandledrejection:可以捕获到promise异常,也是通过window.addEventListener('unhandledrejection', fun, true)方式
-
可以配合配合项目使用的前端框架的错误处理方式来收集,比如VUE的异常处理方法errorHandler,可以通过重写vue.prototype.errorHandler 方法来达到手机框架的一些异常
var defaults = { ua: window.navigator.userAgent, browser: '', os: '', osVersion: '', errUrl: window.location.href, msg: '', // 错误的具体信息 url: '', // 错误所在的url line: '', // 错误所在的行 col: '', // 错误所在的列 error: '' // 具体的error对象 }
Promise
-
promise 是一个对象,对象和函数的区别就是对象可以保存状态,函数不可以(闭包除外)
-
多个异步等待合并便于解决
-
3种状态:pending,fulfilled,rejected
-
常用方法:Promise.all() Promise.race Promise.resolve Promise.reject
-
Promise.all 和 Promise.race 区别
let p = Promise.all([p1,p2,p3])
all():只有p1, p2, p3 的状态都变成fulfilled, p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组传递给p的回调函数
只要有一个rejected,p的状态就变成rejected,此时第一个被reject的实例返回值会传递给p的回调函数
race():只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的返回值。
// 手写Promise
new的过程
var Person = function(){};
var p = new Person();
new 的过程拆分为以下三步
-
var p = {}; 也就是说,初始化一个对象p
-
p._proto_ = Person.prototype;
-
Person.call(p);也就是说构造p, 也称之为初始化p
// 构造器函数 let Parent = function (name, age) { this.name = name; this.age = age; }; Parent.prototype.sayName = function () { console.log(this.name); }; //自己定义的new方法 let newMethod = function (Parent, ...rest) { // 1.以构造器的prototype属性为原型,创建新对象; let child = Object.create(Parent.prototype); // 2.将this和调用参数传给构造器执行 let result = Parent.apply(child, rest); // 3.如果构造器没有手动返回对象,则返回第一步的对象 return typeof result === 'object' ? result : child; }; //创建实例,将构造函数Parent与形参作为参数传入 const child = newMethod(Parent, 'echo', 26); child.sayName() //'echo'; //最后检验,与使用new的效果相同 child instanceof Parent//true child.hasOwnProperty('name')//true child.hasOwnProperty('age')//true child.hasOwnProperty('sayName')//false
手写call,apply,bind
// call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
Function.prototype.call = function(context, ...args){
context = context?context:window;
context.__f = this;
let result = context.__f(...args);
delete context.__f;
return result;
}
// apply() 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。
Function.prototype.apply = function(context, args){
context = context?context:window;
context.__f = this;
let result = context.__f(...args);
delete context.__f;
return result;
}
// bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
Function.prototype.bind = function(context, ...arg1){
context = context?context:window;
context.__f = this;
return function(...args2){
let result = context.__f(...[...args1,...args2]);
delete context.__f;
return result;
}
}
async...await
async 是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联的。async/await 是建立在 promise 的基础上
async/await 让异步代码看起来、表现起来更像同步代码。这正是其威力所在。
闭包
【函数】和【函数内部能访问到的变量】(也叫环境)的总和,就是一个闭包
经常使用的场景中return 的作用是使用闭包,让别人可以间接访问
作用:常常用来【间接访问一个变量】。换句好说,【隐藏一个变量】
// 案列一:
function a(){
let a = "sd";
function b(){
console.log(a);
}
}
// 案列2 return 作用是让别人可以间接访问到a变量
// 闭包:b可以读取a中的变量,只要把b作为返回值,就可以在a外读取a内部变
// 原因:a是b的父函数,b被赋给了一个全局变量,a始终存在内存中,b的存在依赖a,因此a也始终存在内存中,不会在调用结束后,被垃圾回收机制回收。
function a(){
let a = "sd";
return function b(){
console.log(a);
}
}
场景:
-
使用闭包代替全局变量
-
函数外或在其他函数中访问某一函数内部的参数
-
在函数执行之前为要执行的函数提供具体参数
-
在函数执行之前为函数提供只有在函数执行或引用时才能知道的具体参数
-
为节点循环绑定click事件,在事件函数中使用当次循环的值或节点,而不是最后一次循环的值或节点
-
暂停执行
-
包装相关功能
// 1,2
//闭包,test2是局部变量,这是闭包的目的
//我们经常在小范围使用全局变量,这个时候就可以使用闭包来代替。
(function(){
var test2=222;
function outer(){
alert(test2);
}
function test(){
alert("测试闭包:"+test2);
}
outer(); //222
test(); //测试闭包:222
})();
alert(test2); //未定义,这里就访问不到test2
// 3
//正确做法,使用闭包
function f3(obj){return function(){alert(obj)}}
var test2=f3('222');//返回f3的内部函数的引用
setTimeout(test2,500);//正确,222
document.getElementById("hello").onclick=test2;//正确,222
document.getElementById("hello").attachEvent("onclick",test2);//正确,222
//原生的setTimeout传递的第一个函数不能带参数
setTimeout(function(param){
alert(param)
},1000)
//通过闭包可以实现传参效果
function func(param){
return function(){
alert(param)
}
}
var f1 = func(1);
setTimeout(f1,1000);
//用闭包定义能访问私有函数和私有变量的公有函数。
var counter = (function(){
var privateCounter = 0; //私有变量
function change(val){
privateCounter += val;
}
return {
increment:function(){ //三个闭包共享一个词法环境
change(1);
},
decrement:function(){
change(-1);
},
value:function(){
return privateCounter;
}
};
})();
// 6
function sleep(obj,iMinSecond){
if (window.eventList==null) window.eventList=new Array();
var ind=-1;
for (var i=0;i<window.eventList.length;i++){
if (window.eventList[i]==null) {
window.eventList[i]=obj;
ind=i;
break;
}
}
if (ind==-1){
ind=window.eventList.length;
window.eventList[ind]=obj;
}
setTimeout("goon(" + ind + ")",iMinSecond);
}
goo(){}
function test(){
sleep(this,3000);//调用暂停函数
}
原型,原型链
-
所有函数都有
prototype
(原型)属性,所有引用类型都有__proto__
(隐式原型)属性,都是一个普通对象 -
引用类型的
__proto__
指向它的构造函数的prototype
let Person = function(){}
let person = new Person();
person.__proto__ = Person.prototype
person.__proto__.constructor = Person;
Person.prototype.constructor = Person;
当访问对象上的某个属性,会先在对象本身上查找,如果没有找到会继续在它的
__proto__
(隐式原型)上查找,即查找它的构造函数原型,如果还有找到,则继续在原型的__proto__
上查到,直到Object.prototype.__proto__
(为null)停止。这样一层一层的向上查找就形成了一个链式结构(原型链)。另一面证明了万物皆对象
访问链路
为:
作用:
- 继承,减少了内存占用
- prototype用来实现基于原型的继承与属性的共享
- 避免了代码冗余,公用的属性和方法可以提取放在原型中,这样通过改构造函数实例化的所有对象都可以使用该对象的构造函数中的属性和方法
场景:
- 有重复的代码和属性
特点:
- 就近原则,优先使用离自己近的
- 引用类型,当我们使用或者修改原型链上的值时。其实使用的都是同一个值
- 所有函数的默认原型都是Object的实例
类
第一种
funtion People(name, age){
this.name = name;
this.age = age;
}
People.prototype.printInfo = function(){
console.log(`name: ${this.name}, age: ${this.age}`)
}
let people = new People("小明", 16);
people.printInfo(); // name: 小明,age: 16
第二种
const People = {
name:"",
age:"",
printInfo: function(){
console.log(`name: ${name}, age: ${age}`)
}
}
let people = Object.create(People);
people.name = "小明";
people.age = 16;
people.printInfo(); // name: 小明,age: 16
第三种
- 弊端:instanceof 不能判断类
let Botany = {
createNew: function(color) {
let obj = {}
obj.color = color;
obj.printColor = function() {
console.log(this.color);
}
return obj
}
}
let cactus = Botany.createNew("green");
cactus.printColor();
// 继承
let A = {
createNew: function() {
let obj = Botany.createNew();
// ...do something
return obj;
}
}
// 共享
let B = {
isBotany: true,
createNew: function() {}
}
第四种 class 关键字
class Point {
constructor(x, y) {
// ...
}
toString() {
// ...
}
}
var b = new Point(); // 必须使用new 关键字,否则会报错
Object.keys(Point.prototype) // []
Object.getOwnPropertyNames(Point.prototype); // ["constructor", "toString"]
//上面代码中,toString()方法是Point类内部定义的方法,它是不可枚举的。这一点与 ES5 的行为不一致。
var Point = function (x, y) {
// ...
};
Point.prototype.toString = function () {
// ...
};
Object.keys(Point.prototype)
// ["toString"]
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
注意事项
-
私有属性的解决方案
// 第一种 _bar()方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部,还是可以调用到这个方法 class Widget { // 公有方法 foo (baz) { this._bar(baz); } // 私有方法 _bar(baz) { return this.snaf = baz; } // ... } // 第二种 class Widget { foo (baz) { bar.call(this, baz); } // ... } function bar(baz) { return this.snaf = baz; } // 第三种 利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。 const bar = Symbol('bar'); const snaf = Symbol('snaf'); export default class myClass{ // 公有方法 foo(baz) { this[bar](baz); } // 私有方法 [bar](baz) { return this[snaf] = baz; } // ... }; // 提案,不做正式用 // 在属性名之前,使用#表示 class Counter { #xValue = 0; constructor() { super(); // ... } get #x() { return #xValue; } set #x(value) { this.#xValue = value; } }
-
new.target
function Person(name) { if (new.target !== undefined) { this.name = name; } else { throw new Error('必须使用 new 命令生成实例'); } } // 另一种写法 function Person(name) { if (new.target === Person) { this.name = name; } else { throw new Error('必须使用 new 命令生成实例'); } } var person = new Person('张三'); // 正确 var notAPerson = Person.call(person, '张三'); // 报错 // class 注意的是,子类继承父类时,new.target会返回子类 class Rectangle { constructor(length, width) { console.log(new.target === Rectangle); this.length = length; this.width = width; } } var obj = new Rectangle(3, 4); // 输出 true // new.target会返回子类。利用这个特点,可以写出不能独立使用、必须继承后才能使用的类 class Shape { constructor() { if (new.target === Shape) { throw new Error('本类不能实例化'); } } } class Rectangle extends Shape { constructor(length, width) { super(); // ... } } var x = new Shape(); // 报错 var y = new Rectangle(3, 4); // 正确
-
Object.getPrototypeOf 可以从子类上获取父类,可用作判断一个类是否继承自另一个类
Object.getPrototypeOf(ColorPoint) === Point // true
-
super 关键字
- 有时当做函数,有时当做对象
- 当函数使用时:子类中的构造函数顶部必须执行super();super() 相当于 父类.prototype.constructor.call(this);
- 当对象使用时:super.xx 值父类的原型对象上的属性;相当于父类.prototype.xx
- new.target.name 指向的永远是实例本身的构造函数
-
-
NAN
-
Number.NaN
-
Number.isNaN 相比于isNaN 不存在转换类型的行为
-
isNAN
// 通过Number()强类型转换 将val 转成number类型,再判断是否为NaN, 所以 isNaN是判断val是否能转为数字,成功返回false,失败返回true console.log(isNaN(null)); //false console.log(isNaN(true)); //false console.log(isNaN(false)); //false console.log(isNaN(0)); //false console.log(isNaN(undefined)); //true console.log(isNaN("AB")); //true console.log(isNaN({a: 1})); //true console.log(isNaN(NaN)); //true // Number.isNaN() // Es6 新加的严格判断是否 === NaN console.log(Number.isNaN(null)); //false console.log(Number.isNaN(true)); //false console.log(Number.isNaN(false)); //false console.log(Number.isNaN(0)); //false console.log(Number.isNaN(undefined)); //false console.log(Number.isNaN("AB")); //false console.log(Number.isNaN({a: 1})); //false console.log(Number.isNaN(NaN)); //true // 区别 isNaN("123Hello") // true isNaN("123") // false Number.isNaN("123Hello") // false Number.isNaN("123") // false
== 和 ===
==:相等运算符
-
比较相同类型原始值与===一样
-
比较不同类型的数据是
-
如果类型为原始类型,则会将数据转换为数字类型再进行比较
-
对象与原始类型值比较,对象转化为原始类型再进行比较
'' == '0' // false 0 == '' // true 0 == '0' //true false == 'false' //false false == '0' // true false == undefined // false false == null // false null == undefined // true ' \t\r\n ' == 0 // true
-
===:严格运算符
* 不同类型值,如果俩个值得类型不同,直接返回false
* 同一类型的原始类型的值(数值,字符串,布尔值)比较时,值相同就返回true,值不同返回false
* 俩个复合类型(对象,数组,函数)的数据比较时,不是比较他们的值是否相同,而是比较他们是否指向同一个对象(内存地址)
* undefined 和 null 与自身严格相等
类型转换
在 JavaScript 中有 6 种不同的数据类型:
- string
- number
- boolean
- object
- function
- symbol
3 种对象类型:
- Object
- Date
- Array
2 个不包含任何值的数据类型:
- null
- undefined
全局方法String() 可以将数字转换为字符串,该方法可用于任何类型的数字,字母,变量,表达式
Number 方法toString() 也是同样的效果
// toExponential() 把对象的值转换为指数计数法
var num = 5.56789;
var n = num.toExponential(); // 5.56789e+0
// toFixed() 把数字转换为字符串,结果的小数点后有指定位数的数字
// toPrecision() 把数字格式化为指定的长度
// Number.isInteger(): 用来判断给定的参数是否为整数。
// Number.isSafeInteger(): 判断传入的参数值是否是一个"安全整数"。
Number.isInteger(10); // 返回 true
Number.isInteger(10.5); // 返回 false
// typeof 操作符来查看JavaScript 变量的数据类型
typeof "John" // 返回 string
typeof 3.14 // 返回 number
typeof NaN // 返回 number
typeof false // 返回 boolean
typeof [1,2,3,4] // 返回 object
typeof {name:'John', age:34} // 返回 object
typeof new Date() // 返回 object
typeof function () {} // 返回 function
typeof myCar // 返回 undefined (如果 myCar 没有声明)
typeof null // 返回 object
// constructor 属性返回JavaScript变量的构造函数
"John".constructor // 返回函数 String() { [native code] }
(3.14).constructor // 返回函数 Number() { [native code] }
false.constructor // 返回函数 Boolean() { [native code] }
[1,2,3,4].constructor // 返回函数 Array() { [native code] }
{name:'John', age:34}.constructor // 返回函数 Object() { [native code] }
new Date().constructor // 返回函数 Date() { [native code] }
function () {}.constructor // 返回函数 Function(){ [native code] }
// 使用constructor 属性来查看对象是否为数组(包含字符串“Array”)
function isArray(data){
return data.constructor.toString().indexOf("Array") > -1;
}
// 使用constructor 属性来查看对象是否为日期
function isDate(data){
return data.constructor.toString().indexOf("Date") > -1;
}
同步和异步的区别
我们知道 js 是一个单线程的操作,为什么说它是单线程呢?不可以是多线程呢?js是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作dom;这决定了它只能是单线程,否则会带来很复杂的同步问题。 举个例子:如果js被设计了多线程,如果有一个线程要修改一个dom元素,另一个线程要删除这个dom元素,此时浏览器就会一脸茫然,不知所措。所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变
异步运行机制如下:
- 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
- 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
- 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
"异步模式"编程的4种方法
- 回调函数
// setTimeout、setInterval
function fun1(){
setTimeout(()=>{
cb();
})
console.log('fun1');
}
function fun2(){
console.log('fun2');
}
fun1(fun2);
// node
var fs=require('fs');
function getMime(callback){
fs.readFile('mime.json',function(err,data){
callback(data);
})
}
getMime(function(result){
console.log(result.toString());
})
- 事件监听,任务的执行不取决于代码的顺序,而取决于某个事件是否发生
// 监听函数有:on, bind, listen, addEventListener, observe
- 发布订阅模式,也叫观察者模式
我们假定,存在一个“信号中心”,某个任务执行完成
- Promises对象
click 优化
事件冒泡和事件传递
DOM 2 级事件 规定的事件流包含三个阶段:事件捕获阶段,处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,然后是实际的目标接收事件,最后阶段是冒泡阶段
事件传播三个阶段 (Event.prototype)
- 0 NONE: 默认值,不代表任何意思
- 1 CAPTURING_PHASE: 捕获阶段
- 2 AT_TARGET: 目标阶段(当前事件源)
- 3 BUBBLING _PHASE: 冒泡阶段
事件时用户或浏览器自身执行的某种动作,而响应某个事件的函数叫做事件处理程序,HTML事件处理程序。DOM 0 级事件处理程序和IE事件处理程序均以“on”开头,DOM 2 级事件处理程序不需要加“on”
- DOM 0 级事件绑定:
- dom.onclick = function(){}
- DOM 2 级事件绑定
- 标准浏览器: dom.addEventlistener('click', function(){}, false);
- IE 6-8:dom. ('onclick', function(){}), 事件对象通过window.event 来获取
-
事件捕获是指从外层传递到里面,知道当前元素的事件行为
- addEventlistener(eventName, function, true)
-
事件冒泡:当前元素的某个事件行为被处罚,他的所有祖先元素(一直到document)的相关事件行为也会被触发执行(由里向外)
- addEventlistener(eventName, function, false)
window.event? window.event.cancelBubble = true : e.stopPropagation();
- addEventlistener(eventName, function, false)
-
DOM 3 事件中stopImmediatePropagation()方法来阻止事件捕获,另外此方法还可以阻止事件冒泡
-
mouseover 存在冒泡传播机制,mouseenter冒泡传播机智被浏览器阻止
-
阻止事件的默认行为及阻止
- A标签有哪些默认行为
- 超链接:点击A标签可以实现页面的跳转
- 锚点定位:通过HASH值定位到当前页面的指定ID盒子位置
- 首先URL地址栏末尾追加一个HASH;
- 如果地址栏包含hash值,浏览器在渲染页面后,会默认定位到hash值得位置
- 阻止A标签的默认行为
- 在HTML中
<a href="javascript:;" />
; - 在JS中
e=e||window.event; e.preventDefault?e.preventDefault:e.returnValue = false;
- 在HTML中
- A标签有哪些默认行为
-
当一个容器内的很多元素都要为同一事件绑定方法,那么我们只需要给外层容器的该事件绑定方法,当里层元素的事件被出发时,会通过冒泡传播机制传到最外层容器那里,触发外层容器绑定的方法执行,在方法执行时,我们只需要根据判断事件源的不同而做不同的事情。(利用事件委托可提高50%左右的性能)
-
onDOMContentLoaded
//当前浏览器中的DOM结构加载完成,就会触发这个事件 -
readystatechange
// IE6-8下使用
this问题 标准:当事件行为被触发,方法中的this指向当前元素本身 IE6-8:当事件行为被触发,方法中的this指向window
五种主要数据类型的克隆(Number, String, Object, Array, Boolean)
- 使用typeof 判断值得类型;
- 使用toString区分数组和对象
- 使用递归函数
function clone(obj){
if(typeof obj === 'object' && obj !== 'null'){
var cloneObj = Obejct.prototype.toString.call(obj).slice(8, -1) === 'Array'?[]:{};
for(let k in obj){
if(typeof obj[k] === 'object' && obj[k] !== 'null'){
cloneObj[k] = clone(obj[k]);
}else{
cloneObj[k] = obj[k]
}
}
}else{
return obj;
}
return cloneObj;
}
伪数组,如何转化为标准数组
伪数组是一个Object,存在的意义是可以让普通的对象也能正常使用数组的很多方法。比如:
let obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
}
;[].push.call(obj, 'd')
console.log([].slice.call(obj))
;[].forEach.call(obj, function(num, index){
console.log(num)
})
常见的伪数组有:
- 函数内部的arguments
- DOM对象列表(比如通过document.getElemntsByTags 得到的列表)
- jQuery 对象(比如 $("div")) 不同
- 对象数组没有Array.prototype 属性, 类型是Obejct,而数组类型是Array
- 数组是基于索引的实现,length会自动更新,而对象时键值对
- 使用对象可以创建伪数组,伪数组可以使用数组的大部分方法
call 和 apply 区别
javascript
中 的每一个Function
对象都有一个apply()
方法和 call()
方法
定义
- apply:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.apply(A, arguments); 即A对象应用B对象的方法
- call:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.call(A, args1,args2);即A对象调用B对象的方法。
不同
- apply:最多只能有两个参数--新this对象和一个数组argArray,如果给改方法传递多个参数,则把参数都写入这个数组里面,当然,即使只有一个参数,也要写进数组里,如果argArray不是一个有效的数组和arguments对象,那么将导致一个TypeError。如果没有提供argArray 和 thisObj 任何一个参数,那么Global对象将被用作thisObj,并且无法被传递任何参数。
- call:它可以接受多个参数,第一个参数与apply一样,后面则是一串参数列表。这个方法主要用在js对象各方法相互调用的时候,使当前this实例指针保持一致,或者在特殊情况下改变this指针。没有提供thisObj参数,那么Global 对象被用作thisObj。
- apply和call的功能是一样的,只是传入的参数列表形式不同
function add(a,b){
return a+b;
}
function sub(a,b){
return a-b;
}
var a1 = add.apply(sub,[4,2]); //sub调用add的方法
var a2 = sub.apply(add,[4,2]);
alert(a1); //6
alert(a2); //2
/*call的用法*/
var a1 = add.call(sub,4,2);
callee和caller的作用
function factorial(x) {
return x<=1 ? 1 : x*factorial(x-1);
}
// 使用 callee , callee 是arguments对象的一个属性,指向arguments对象的函数
function factorial(x) {
return x<=1 ? 1 : x*arguments.callee(x-1);
}
// caller 函数对象的一个属性,指向调用当前函数的函数,比如A()调用B(),则在B()中B.caller指向A();
function B(){
console.log(B.caller); // A();
}
(function A(){
B()
})()
// 如果c的执行环境为window则返回null
var c = function(x,y) {
console.log(arguments.length,arguments.callee.length,arguments.callee)
// arguments.callee.length 形参的长度
} ;
c(1,2,3) ; // 3, 2, function(x, y){}
注:在箭头函数中,this作用域跟函数外是一致的,且没有arguments对象,所以callee和caller在箭头函数中是无效的
instanceof
instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。
ES5 对象方法
// 添加或更改对象属性
Object.defineProperty(object, property, descriptor)
// 添加或更改多个对象属性
Object.defineProperties(object, descriptors)
// 访问属性
Object.getOwnPropertyDescriptor(object, property)
// 以数组返回所有属性
Object.getOwnPropertyNames(object)
// 以数组返回所有可枚举的属性
Object.keys(object)
// 访问原型
Object.getPrototypeOf(object)
// 阻止向对象添加属性
Object.preventExtensions(object)
// 如果可将属性添加到对象,则返回 true
Object.isExtensible(object)
// 防止更改对象属性(而不是值)
Object.seal(object)
// 如果对象被密封,则返回 true
Object.isSealed(object)
// 防止对对象进行任何更改
Object.freeze(object)
// 如果对象被冻结,则返回 true
Object.isFrozen(object)
JS 引擎的编译流水线是什么 渲染流程都做了什么 为什么需要 event loop 不同的 JS 宿主环境有哪些不同 micro task 和 check 都解决了什么问题 requestAnimationFrame 是宏任务还是微任务 requestIdleCallback 是什么时候执行的