一道异步好题

setTimeout(() => {
        console.log('异步1任务time1');
        new Promise(function (resolve, reject) {
            console.log('异步1宏任务promise');
            setTimeout(() => {
                console.log('异步1任务time2');
            }, 0);
            resolve();
        }).then(function () {
            console.log('异步1微任务then')
        })
    }, 0);
    console.log('主线程宏任务');
    setTimeout(() => {
        console.log('异步2任务time2');

    }, 0);
    new Promise(function (resolve, reject) {
        console.log('宏任务promise');
        // reject();
        resolve();
    }).then(function () {
        console.log('微任务then')
    }).catch(function () {
        console.log('微任务catch')
    })
    console.log('主线程宏任务2');
最后结果为:
宏任务微任务的问题 脑海中一定要有那三个数据结构:stack   macrotask queue   microtask queue
例中需注意第9行的then是在第14行的setTimeout之前执行的,而第5行的setTimeout在第14行之后执行。
也就是在一个异步任务代码块中,会先执行完所有同步语句和微任务语句,然后去执行整个代码中的其他同级别的异步任务(在macrotask queue里排队呢)
而第5行的setTimeout因是第二层异步语句,会被放到之后才执行。

JSONP

多看几次 不要再在这个上面犯错了
var script = document.createElement('script');
script.type = 'text/javascript';

// 传参并指定回调执行函数为onBack
script.src = 'http://www.....:8080/login?user=admin&callback=onBack';
document.head.appendChild(script);

// 回调执行函数
function onBack(res) {
    alert(JSON.stringify(res));
}

W3C中定义了事件的发生经历三个阶段:

1:捕获阶段 ---> 2:目标阶段 ---> 3:冒泡阶段
document ---> target目标 ----> document
通过addEventListener的第三个参数来设置:
true 表示该元素在事件的“捕获阶段”(由外往内传递时)响应事件
false 表示该元素在事件的“冒泡阶段”(由内向外传递时)响应事件  【默认是flase】
冒泡型事件:当你使用事件冒泡时,子级元素先触发,父级元素后触发 阻止冒泡使用 stopPropagation()
捕获型事件:当你使用事件捕获时,父级元素先触发,子级元素后触发 阻止捕获使用 preventDefault()

事件执行顺序:判断的关键是否目标元素:
  • 非目标元素:根据W3C的标准执行:捕获->目标元素->冒泡(不依据事件绑定顺序)
  • 目标元素:依据事件绑定顺序:先绑定的事件先执行(不依据捕获冒泡标准)
  • 最终顺序:父元素捕获->目标元素事件1->目标元素事件2->子元素捕获->子元素冒泡->父元素冒泡
  • 注意:子元素事件执行前提 事件确实“落”到子元素布局区域上,而不是简单的具有嵌套关系

你觉得jQuery源码有哪些写的好的地方

  • jquery源码封装在一个匿名函数的自执行环境中,有助于防止变量的全局污染,然后通过传入window对象参数,可以使window对象作为局部变量使用,好处是当jquery中访问window对象的时候,就不用将作用域链退回到顶层作用域了,从而可以更快的访问window对象。同样,传入undefined参数,可以缩短查找undefined时的作用域链
  • jquery将一些原型属性和方法封装在了jquery.prototype中,为了缩短名称,又赋值给了jquery.fn,这是很形象的写法
  • 有一些数组或对象的方法经常能使用到,jQuery将其保存为局部变量以提高访问速度
  • jquery实现的链式调用可以节约代码,所返回的都是同一个对象,可以提高代码效率

Node的应用场景

特点:
  • 1、它是一个Javascript运行环境
  • 2、依赖于Chrome V8引擎进行代码解释
  • 3、事件驱动
  • 4、非阻塞I/O
  • 5、单进程,单线程

优点:高并发(最重要的优点)
缺点:
1、只支持单核CPU,不能充分利用CPU
2、可靠性低,一旦代码某个环节崩溃,整个系统都崩

es6模块 CommonJS、AMD、CMD

commonJS
CommonJS是服务器端模块的规范 加载模块是同步的
demo:
//模块定义 myModel.js var ......  module.exports = { printName: printName}  //加载模块  var nameModule = require('./myModel.js'); nameModule.printName();
在浏览器中会出现堵塞情况

AMD
异步模块定义 解决了同步加载的问题
需要定义回调define方式
 demo:
// 定义模块 myModule.js
define(['dependency'], function(){
    var name = 'Byron';
    function printName(){
        console.log(name);
    }

    return {
        printName: printName
    };
});

// 加载模块
require(['myModule'], function (my){
  my.printName();
});
AMD和CMD的对比:
AMD在加载完成定义好的模块
就会立即执行,所有执行完成后,遇到require才会执行主逻辑。(提前加载)


CMD在加载完成定义好的模块,仅仅是下载不执行,在遇到require才会执行对应的模块。(按需加载)


CMD
通用模块定义 和AMD解决同样的问题 但运行机制不
demo:
// 定义模块  myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
});

// 加载模块
seajs.use(['myModule.js'], function(my){

});

es6
结合了commonjs和AMD的优点
也就是一直在用的import/export
export function test (args) {
  // body...
  console.log(args); 
}
  
// 默认导出模块,一个文件中只能定义一个
export default function() {...};
export const name = "lyn";

// _代表引入的export default的内容
import _, { test, name } from './a.js';  
test(`my name is ${name}`);
es6输出的是值的引用
并且是静态引入 编译时就引入了

造成内存泄漏的操作

内存泄漏指任何对象在您不再拥有或需要它之后仍然存在
如:
  • setTimeout 的第一个参数使用字符串而非函数
  • 闭包
  • 控制台日志
  • 循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)
  • 移除存在绑定事件的DOM元素(IE)

javascript创建对象的几种方式

这个就还挺随便的 反正就是把类似class那些方法用一用
字面量直接声明
person={firstname:"Mark", :"Yun",age:25, eyecolor:"black"};
new一下
var obj = new Object();
Object.create( )
var obj = Object.create(Object.prototype);
用来指定原型
用function
模拟无参的构造函数
function Person(){}
	var person=new Person();
//定义一个function,如果使用new"实例化"
//该function可以看作是一个Class
        person.name="Mark";
        person.age="25";
        person.work=function(){
        alert(person.name+" hello...");
}
(这不就是class吗?)

模拟参数构造函数
function Pet(name,age,hobby){
       this.name=name;//this作用域:当前对象
       this.age=age;
       this.hobby=hobby;
       this.eat=function(){
           alert("吃了");
       }
}
实例化:
var maidou =new Pet("麦兜",25,"coding");
工厂方式
var wcDog =new Object();
     wcDog.name="旺财";
     wcDog.age=3;
     wcDog.work=function(){
       alert("我是"+wcDog.name+",汪汪汪......");
     }


原型方式
function Dog(){}
Dog.prototype.name="旺财";
Dog.prototype.eat=function(){
	alert(this.name+"是个吃货");
}
var wangcai =new Dog();
混合方式
 function Car(name,price){
	this.name=name;
	this.price=price;
}
Car.prototype.sell=function(){
	alert
    ("我是"+this.name+",我现在卖"+this.price+"万元");
}
var camry =new Car("凯美瑞",27);

null undefined

null
null 是一个对象(空对象, 没有任何属性和方法)
在验证null时,一定要使用 === ,因为 ==无法分别null 和 undefined
undefined
undefined 表示不存在这个
例如变量被声明了,但没有赋值时,就等于undefined

script异步加载方式

设置<script>属性 defer="defer"  
设置<script>属性 async="async"
动态创建 var script = document.createElement('script')
XmlHttpRequest 脚本注入
异步加载库 LABjs

其中 defer async两者区别是:
defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;
async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。
一句话,defer是“渲染完再执行”,async是“下载完就执行”。
另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。

eval

它的功能是把对应的字符串解析成JS代码并运行
应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)
由JSON字符串转换为JSON对象的时候可以用eval,var obj =eval('('+ str +')')

let var const

之前记错了!气死了!
let const var 都有全局作用域和函数作用域
但是var没有块级作用域罢了
在全局上下文中 
  • 带var的变量是 声明一个全局变量,不能被delete删除
  • 不带var的变量是 创建一个全局对象(window)的属性,可以用delete关键字删除
在函数上下文中
  • 带var的变量是 声明一个私有变量
  • 不带var的变量,处理机制是
    • 沿作用域链向上查找该变量,是哪个上下文中声明的变量,就改变哪个上下文中的变量
    • 如果所有上下文中都没有该变量,则给全局对象 window,添加一个同名属性
let
  • 允许你声明一个作用域被限制在块级中的变量、语句或者表达式
  • let绑定不受变量提升的约束,这意味着let声明不会被提升
  • 该变量处于从块开始到初始化处理的“暂存死区”
var
  • 声明变量的作用域限制在其声明位置的上下文中
  • var声明的变量知识没有块级作用域的限制 而非声明变量总是全局的
  • 有变量提升
这个是之前弄错的一点
人家var声明在函数里就是函数作用域  声明在函数外 才是全局作用域 声明一个全局变量
const
  • 声明创建一个值的只读引用 (即指针)
  • const声明基本数据类型时 再改变其值 会报错
  • 但是声明复合类型,只改变复合类型中的某个value项,还可以正常使用
demo:
声明 const arr =[1,2,3]后 不能通过 arr=[4,2,3]来重新赋值
但是可以通过arr[0]=4 来进行改变
如果没有块级作用域 则会出现很多不合理的场景
如 内层变量覆盖外层变量 此时声明在if中的tmp会被提升为函数作用域的变量
var tmp = new Date();

function f(){
  console.log(tmp);
  if(false){
    var tmp = "hello";
  }
}
f(); // undefined
还可能出现 用来记录的循环变量泄露为全局变量
此时的i只是用来记录循环的 但是用var来记录 循环结束后不会消失 反而泄露了
var s = "hello";

for(var i=0;i++)
  console.log(s[i]);
}
console.log(i); // 5

 attribute和property的区别是什么

  • attribute是dom元素在文档中作为html标签拥有的属性  比如如‘type’,'id','value','classNamle'以及自定义属性,它的值只能是字符串。
  • property就是dom元素在js中作为对象拥有的属性。可以直接使用DOM属性的方式进行操作 属性值为多类型
  • 对于html的标准属性来说,attribute和property是同步的,是会自动更新的
  • 但是对于自定义的属性来说,他们是不同步的
并且 property:修改对象属性 不会体现到html结构中   attribute:修改html属性 并且会改变html结构

如何渲染几万条数据并不卡住界面

这条是重点 之后多看看!
不能一次性将几万条都渲染出来,而应该一次渲染部分 DOM,那么就可以通过 requestAnimationFrame 来每 16 ms 刷新一次
<body>
  <ul>控件</ul>
  <script>
    setTimeout(() => {
      // 插入十万条数据
      const total = 100000
      // 一次插入 20 条,如果觉得性能不好就减少
      const once = 20
      // 渲染数据总共需要几次
      const loopCount = total / once
      let countOfRender = 0
      let ul = document.querySelector("ul");
      function add() {
        // 优化性能,插入不会造成回流
        const fragment = document.createDocumentFragment();
        for (let i = 0; i < once; i++) {
          const li = document.createElement("li");
          li.innerText = Math.floor(Math.random() * total);
          fragment.appendChild(li);
        }
        ul.appendChild(fragment);
        countOfRender += 1;
        loop();
      }
      function loop() {
        if (countOfRender < loopCount) {
          window.requestAnimationFrame(add);
        }
      }
      loop();
    }, 0);
  </script>
</body>

callee caller

callee
返回正在执行的函数本身的引用,它是arguments的一个属性
demo:
function a() {
  console.log(arguments.callee)
}
  • 这个属性只有在函数执行时才有效
  • 它有一个length属性,可以用来获得形参的个数,
  • 可以用来比较形参和实参个数是否一致,即比较arguments.length是否等于arguments.callee.length
  • 它可以用来递归匿名函数。
caller
返回一个对函数的引用,该函数调用了当前函数
demo:
function a() {
  console.log(a.caller)
}
  • 这个属性只有当函数在执行时才有用
  • 如果在javascript程序中,函数是由顶层调用的,则返回null
也就是 functionName.caller: functionName是当前正在执行的函数。

数组去重汇总

























垃圾回收问题

标记清除
当变量进入执行环境的时
候比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”
当变量离开环境的时候(函数执行结束)将其标记为“离开环境”

垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记
然后去掉环境中的变量以及被环境中变量所引用的变量(闭包)
在这些完成之后仍存在标记的就是要删除的变量了
引用计数
引用计数的策略是跟踪记录每个值被使用的次
当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1
如果该变量的值变成了另外一个,则这个值得引用次数减1
当这个值的引用次数变为0时,可以将其占用的空间回收

this

this,函数执行的上下文,可以通过apply,call,bind改变this的指向。
对于匿名函数或者直接调用的函数来说,this指向全局上下文(浏览器为window,NodeJS为global),
剩下的函数调用,那就是谁调用它,this就指向谁。
箭头函数的指向取决于该箭头函数声明的位置,在哪里声明,this就指向哪里







axios、fetch

axios
demo:
axios({
    method: 'post',
    url: '/user/12345',
    data: {
        firstName: 'Fred',
        lastName: 'Flintstone'
    }
})
.then(function (response) {
    console.log(response);
})
.catch(function (error) {
    console.log(error);
});
  • 从浏览器中创建 XMLHttpRequest
  • 从 node.js 发出 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求和响应数据
  • 取消请求
  • 自动转换JSON数据
  • 客户端支持防止CSRF/XSRF
fetch
demo:
try {
  let response = await fetch(url);
  let data = response.json();
  console.log(data);
} catch(e) {
  console.log("Oops, error", e);

}
  • fetcht只对网络请求报错,对400,500都当做成功的请求,需要封装去处理
  • fetch默认不会带cookie,需要添加配置项
  • fetch不支持abort,不支持超时控制
  • 使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行
  • fetch没有办法原生监测请求的进度,而XHR可以

检测浏览器版本方法

  • 根据 navigator.userAgent    UA.toLowerCase().indexOf('chrome')
  • 根据 window 对象的成员 'ActiveXObject' in window

JS内置对象

  • 数据封装类对象:Object、Array、Boolean、Number、String
  • 其他对象:Function、Arguments、Math、Date、RegExp、Error
  • ES6新增对象:Symbol、Map、Set、Promises、Proxy、Reflect

如何删除一个cookie