1、call、apply、bind的区别
这三个都是用来定义上下文的,call、apply会指定上下文并执行函数;而bind终身定 死上下文但是不执行函数,并返回新的函数。 其中call和apply传入参数的形式有别,call是单独罗列,逗号隔开参数;apply是数 组。 函数.call(上下文对象,参数,参数,参数); 函数.apply(上下文对象,[参数,参数,参数]);
var obj = { a: 10 } function fun(b, c){ console.log(this.a + b + c); } fun.call(obj, 3, 4); fun.apply(obj, [3, 4]); fun = fun.bind(obj); // 返回新的函数 fun(3,4);
2、数据类型有哪些
基本类型:数字number、字符串string、布尔boolean、undefined、null、symbol
引用类型:数组array、函数function、对象object
3、如何检测数据类型
typeof 能够检测:数字、字符串、布尔、undefined、symbol、function
instanceof 能够检测:数组
Object.prototype.toString.call() 万能法
4、各语句的区别
4.1、for和for...in和for...of的区别
for循环,遍历整个数组
for...in加强循环,不光可以遍历数组,还可以遍历对象和其原型上的方法
for...of遍历数组和可枚举的对象
4.2、switch和if的区别
switch用于判断精准的值
if用于判断值的范围
4.3、while和do...while的区别
while当符合条件时则执行
do...while先执行一次,然后再判断是否符合条件,比while要多执行一次
4.4、break和continue的区别
break是跳出当前循环并终止循环
continue是跳出当前循环并执行下一次循环
5、闭包
闭包就是函数能够记忆住当初定义时候的作用域,不管函数到哪里执行了,永远都能够 记住那个作用域,并且会遮蔽新作用域的变量。可预测状态容器;实现模块化,实现变量的私有封装;可以实现迭代器。 闭包缺点:1.闭包有一个非常严重的问题,那就是内存浪费问题,这个内存浪费不仅仅 因为它常驻内存,更重要的是,对闭包的使用不当的话会造成无效内存的产生;2.性能问题 使用闭包时,会涉及到跨作用域访问,每次访问都会导致性能损失。 因此在脚本中,最好小心使用闭包,它同时会涉及到内存和速度问题。不过我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。
function foo(){ var a = 0; function f(){ a++; return a; } return f; } var res = foo(); res(); res(); res(); console.log(res()); // 4 a的值被存储在内存中不会被释放掉
6、原型和原型链
原型:每一个对象类型都有一个隐式原型__ proto __ ,每一个函数都有一个显示原型prototype,该属性指向它的原型对象。
原型链:某个对象的原型又有自己的原型,直到某个对象的原型为null为止,组成这条的最后一环,这种一级一级的链就是原型链。
7、继承
7.1、原型链继承
/** * 缺点:引用类型的属性被所有实例共享, * 在创建Child 的实例时, 不能向Person传参 */ function Person() { this.name = "xiaopao"; } Person.prototype.getName = function () { console.log(this.name); }; function Child() {} Child.prototype = new Person();
7.2、借用构造函数继承(经典继承)
/* 优点: 1.避免了引用类型的属性被所有实例共享 2.可以在Child中向Parent传参 缺点: 1.只是子类的实例,不是父类的实例 2.方法都在构造函数中定义,每次创建实例都会创建一遍方法 */ function Child() { Person.call(this); }
7.3、组合继承
/* 优点:融合原型链继承和构造函数的优点,是JavaScript中最常用的继承模式 缺点:调用了两次父类构造函数 组合继承最大的问题是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子 类型原型的时候,另一次是在子类型构造函数内部) */ function Child(name, age) { Parent.call(this, name); // 第二次调用 Parent() this.age = age; } Child.prototype = new Parent(); // 第一次调用 Parent()
7.4、原型式继承
// 缺点: 包含引用类型的属性值始终都会共享相应的值, 这点跟原型链继承一样 function CreateObj(o) { function F() {} F.prototype = o; return new F(); } var person = { name: "xiaopao", friend: ["daisy", "kelly"], }; var person1 = CreateObj(person);
7.5、寄生式继承 可以理解为在原型式继承的基础上增加一些函数或属性
// 缺点:跟借用构造函数一样,每次创建对象都会创建一遍方法 var ob = { name: "xiaopao", friends: ["lulu", "huahua"], }; function CreateObj(original){ var clone = Object.create(original); //通过调用函数创建一个新对象 clone.sayHi = function(){ //以某种方式来增强这个对象 alert("Hi"); }; return clone; //返回这个对象 } // 上面CreateObj函数 在ECMAScript5 有了一新的规范写法,Object.create(ob) 效果是一样的 , 看下面代码 var ob1 = CreateObj(ob); console.log(ob1.name); // xiaopao
7.6、寄生组合式继承
// 优点:完美继承 // 缺点:代码繁多,使用起来十分麻烦 function Parent(name) { this.name = name; } Parent.prototype.sayName = function () { console.log(this.name); }; function Child(name) { Parent.call(this, name); } function CreateObj(o) { function F() { } F.prototype = o; return new F(); } function prototype(child, parent) { var prototype = CreateObj(parent.prototype); prototype.constructor = child; child.prototype = prototype; } prototype(Child, Parent); var child = new Child("大圣"); child.sayName(); console.log(child);
7.7、es6 继承
class Child extends Parent {}
8、递归和递归优化
递归就是函数自己调用自己。但是又不能无限的调用自己,需要有一个出口,否则会成为死循环。函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
// 循环求1-5的所有数的和 var sum = 0; for(var i = 1; i <= 5; i++){ sum += i; } console.log(sum) // 15 //递归实现1-5的所有数的和 function sum(n){ if(n === 1){ return 1; } return n + sum(n-1); } console.log(sum(5)); //15
尾递归优化是解决递归调用栈溢出的方法。尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
// 上例递归进行尾递归优化 function sum(n, m = 0){ if(n === 1){ return 1 + m; } return sum(n-1, n + m); } console.log(sum(5)); //15 // 或者while优化 function sum(n, m = 0){ while(n >= 1){ return sum(n - 1, n + m); } return m; } console.log(sum(5)); // 15
9、ajax工作原理和封装
1.创建XMLHttpRequest对象。 2.设置请求方式。open() 3.调用回调函数。onreadystatechange 4.发送请求。send()
function ajax(options) { const { type, dataType, data, timeout, url, success, error } = options; var params = formatParams(data); var xhr; //考虑兼容性 if (window.XMLHttpRequest) { xhr = new XMLHttpRequest(); } else if (window.ActiveObject) { //兼容IE6以下版本 xhr = new ActiveXobject("Microsoft.XMLHTTP"); } //启动并发送一个请求 if (type == "GET") { xhr.open("GET", url + "?" + params, true); xhr.send(); } else if (type == "POST") { xhr.open("post", url, true); //设置表单提交时的内容类型 //Content‐type数据请求的格式 xhr.setRequestHeader( "Content‐type", "application/x‐www‐form‐urlencoded" ); xhr.send(params); } // 设置有效时间 setTimeout(function () { if (xhr.readySate != 4) { xhr.abort(); } }, timeout); // 接收 // options.success成功之后的回调函数 options.error失败后的回调函数 //xhr.responseText,xhr.responseXML 获得字符串形式的响应数据或者XML形式的响应数据 xhr.onreadystatechange = function () { if (xhr.readyState == 4) { var status = xhr.status; if ((status >= 200 && status < 300) || status == 304) { success && success(xhr.responseText, xhr.responseXML); } else { error && error(status); } } }; } //格式化请求参数 function formatParams(data) { var arr = []; for (var name in data) { arr.push( encodeURIComponent(name) + "=" + encodeURIComponent(data[name]) ); } arr.push(("v=" + Math.random()).replace(".", "")); return arr.join("&"); }
10、跨域
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。其 实我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。同源策略SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR 等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地 址,也非同源。
方法1:跨域资源共享CORS跨域,就是服务端在HTTP返回头上加上“AccessControll-Allow-Origin:*”。 “Access-Controll-Allow-METHODS:GET, POST” DELETE、PATCH请求类型会发出OPTIONS预检请求。
方法2:代理跨域,webpack-dev-server里面的proxy配置项。config中的 ProxyTable
方法3:JSONP,利用页面srcipt没有跨域限制的漏洞,用script的src引入它,然后页 面内定义回调函数,jQuery中$.ajax({dataType: ‘jsonp’})。
方法4: iframe跨域,配合window.name或者 location.hash或者document.domain 一起使用
方法5:nginx反向代理接口跨域,通过nginx配置一个代理服务器(域名与domain1 相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中 domain信息,方便当前域cookie写入,实现跨域登录。
方法6:jquery的ajax跨域,dataType:'jsonp'
11、事件流和事件委托
事件流一般分三个阶段:1、捕获阶段(由外向内) 2、目标阶段 (执行阶段) 3、冒泡阶段(由内向外)
阻止事件冒泡e.stopPropagation() 阻止默认动作e.preventDefault()
事件委托:就是把事件委托给父级,利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
12、事件循环
同步任务进入主线程,异步任务进入Event Table并注册函数 当指定的事情完成时,Event Table会将这个函数移入Event Queue。 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执 行。 上述过程会不断重复,也就是常说的Event Loop(事件循环)。
console.log("script start"); setTimeout(function () { console.log("setTimeout"); }, 0); Promise.resolve() .then(function () { console.log("promise1"); }) .then(function () { console.log("promise2"); }); console.log("script end"); /* 执行结果为:script start, script end, promise1, promise2, setTimeout因为Promise是微任务,主线程会在同步任务做完后先清空微任务队列,再执行宏任务队列 */
微任务是由JavaScript自身发起,包括:process.nextTick、promise、MutationObserver
宏任务是由宿主发起的,如浏览器、node。包括:setTimeout、setInterval、setImmediate、postMessage
13、防抖和节流
// 节流:在计时器内部清除计时器,有节奏的执行事件 function throttle(callback, delay = 1000){ let timer = null; function f(){ if(!timer){ timer = setTimeout(() => { callback && callback.call(this); clearTimeout(timer); timer = null; }, delay); } } return f; } // 防抖:在计时器前边清除计时器,只执行最后一次事件,能够无限延长执行时间 function debounce(callback, delay = 1000) { let timer = null; function f() { clearTimeout(timer); timer = setTimeout(() => { callback && callback.call(this); }, delay); } return f; }
14、深克隆和浅克隆
浅克隆:同值也同址。浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象。如:Object.assign;=等号赋值;slice截取。
深克隆:同值不同址。深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。如:JSON.parse(JSON.stringify());
function deepClone(target) { // 定义一个变量 let result; // 如果当前需要深拷贝的是一个对象的话 if (typeof target === "object") { // 如果是一个数组的话 if (Array.isArray(target)) { result = []; // 将result赋值为一个数组,并且执行遍历 for (let i in target) { // 递归克隆数组中的每一项 result.push(deepClone(target[i])); } // 判断如果当前的值是null的话;直接赋值为null } else if (target === null) { result = null; // 判断如果当前的值是一个RegExp对象的话,直接赋值 } else if (target.constructor === RegExp) { result = target; } else { // 否则是普通对象,直接for in循环,递归赋值对象的所有值 result = {}; for (let i in target) { result[i] = deepClone(target[i]); } } // 如果不是对象的话,就是基本数据类型,那么直接赋值 } else { result = target; } // 返回最终结果 return result; }
15、cookie、sessionStorage和localStorage的区别
15.1、cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下 15.2、存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大 15.3、数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭 15.4、作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的 15.5、web Storage支持事件通知机制,可以将数据更新的通知发送给监听者 15.6、web Storage的api接口使用更方便
16、get和post请求的区别
GET在浏览器回退时是无害的,而POST会再次提交请求。
GET产生的URL地址可以被Bookmark,而POST不可以。
GET请求会被浏览器主动cache,而POST不会,除非手动设置。
GET请求只能进行url编码,而POST支持多种编码方式。
GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
GET请求在URL中传送的参数是有长度限制的,而POST么有。
对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
GET参数通过URL传递,POST放在Request body中
17、new操作符都做了哪些事情
构造函数中没有显示的创建Object对象,实际上后台自动创建了一个空对象,直接给this对象赋值属性和方法,this即指向创建的对象。没有return返回值,后台自动返回了该对象,该对象继承构造函数的原型
// 模拟构造函数实现 var Book = function(name) { this.name = name; }; //正常用法 var js = new Book('js'); //使用代码模拟,在非IE浏览器中测试,IE浏览器不支持 var javascript = {}; javascript.__proto__ = Book.prototype; Book.call(javascript, 'js');
18、XSS攻击和CSRF攻击
XSS:跨站脚本攻击Cross site script,因叫css容易让人误会所以改成了xss。比如一个JSON数据:
var obj = [ { id: 1, name: "<script>alert('哈哈哈')</script>", age: 12, } ];
在不该出现script代码的地方出现了,引发一些潜在的危险。 XSS漏洞,能让人们在网页里面插入一段有功能的语句。 XSS 全称“跨站脚本”,是注入攻击的一种。其特点是不对服务器端造成任何伤害, 而是通过一些正常的站内交互途径,例如发布评论,提交含有 JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本,作为内容发布到了页面上,其他用户访问这个 页面的时候就会运行这些脚本。 防范: ① 用正则表达式阻止用户提交带有<、eval、script等危险字眼的语句 ② 显示的时候不要直接用innerHTML,而是用innerText,或者将<转义。
CSRF 的全称是“跨站请求伪造”,而 XSS 的全称是“跨站脚本”。看起来有点相 似,它们都是属于跨站攻击——不攻击服务器端而攻击正常访问网站的用户,但前面说 了,它们的攻击类型是不同维度上的分类。CSRF 顾名思义,是伪造请求,冒充用户在站内 的正常操作。我们知道,绝大多数网站是通过 cookie 等方式辨识用户身份(包括使用服务 器端 Session 的网站,因为 Session ID 也是大多保存在 cookie 里面的),再予以授权的。 所以要伪造用户的正常操作,最好的方法是通过 XSS 或链接欺骗等途径,让用户在本机(即 拥有身份 cookie 的浏览器端)发起用户所不知道的请求。 就是说,如果用户不老老实实写姓名,写了一个个<script>叫做XSS。如果进一步的,写了一个$.post()发了document.cookie就是CSRF了。解决方法: ① 用token验证,验证用户的IP地址生成MD5码,更安全的验证方法 ② 防住XSS。
19、垃圾回收机制
一般来说没有被引用的对象就是垃圾,就是要被清除, 有个例外如果几个对象引用形成一个环,互相引用,但根本访问不到它们,这几个对象也是垃圾,也要被清除。 垃圾回收的方法主要有两种:一种是标记清除,即用完之后的变量 赋值成null,另一种 是引用计数,将使用完的对象的引用计数,如果为0则回收
20、常用DOM操作
createElement 创建
appendChild末尾添加
insertBefore 前边插入
cloneNode(true) 克隆
removeChild() 移除
parentNode父节点
childNodes // 全部子节点
firstChild // 第一个子节点
lastChild // 最后一个子节点
previousElementSibling// 上一个兄弟节点
nextElementSibling// 下一个兄弟节点
获取dom节点:document.getElementById() 、document.getElementsByTagName() 、document.getElementsByClassName() 、document.getElementsByName() 、document.querySelector() 、document.querySelectorAll()
21、AMD、CMD、ES6、CommonJS的区别
CommonJS:模块引用(require) 模块输出(exports) 模块标识(module) ES6:模块引用(import) 模块输出(export) 前者支持动态导入,也就是 require(${path}/xx.js),后者目前不支持。 前者是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线 程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同 步导入会对渲染有很大影响。 前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以 如果想更新值,必须重新导入一次。但是后者采用实时绑定的方式,导入导出的值都 指向同一个内存地址,所以导入值会跟随导出值变化 AMD、CMD都使用define定义模块,require引入模块,区别在于AMD是前置依赖, CMD是就近依赖
// AMD 依赖必须一开始就声明 define(["./a", "./b"], function (require, factory) { // do something... }); // CMD define(function(require, factory) { var a = require('./a'); // 依赖就近书写 // do something... });