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、原型链继承
function Person() {
this.name = "xiaopao";
}
Person.prototype.getName = function () {
console.log(this.name);
};
function Child() {}
Child.prototype = new Person();
7.2、借用构造函数继承(经典继承)
function Child() {
Person.call(this);
}
7.3、组合继承
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");
微任务是由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: "",
age: 12,
}
];
在不该出现script代码的地方出现了,引发一些潜在的危险。 XSS漏洞,能让人们在网页里面插入一段有功能的语句。 XSS 全称“跨站脚本”,是注入攻击的一种。其特点是不对服务器端造成任何伤害, 而是通过一些正常的站内交互途径,例如发布评论,提交含有 JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本,作为内容发布到了页面上,其他用户访问这个 页面的时候就会运行这些脚本。 防范: ① 用正则表达式阻止用户提交带有<、eval、script等危险字眼的语句 ② 显示的时候不要直接用innerHTML,而是用innerText,或者将<转义。
CSRF 的全称是“跨站请求伪造”,而 XSS 的全称是“跨站脚本”。看起来有点相 似,它们都是属于跨站攻击——不攻击服务器端而攻击正常访问网站的用户,但前面说 了,它们的攻击类型是不同维度上的分类。CSRF 顾名思义,是伪造请求,冒充用户在站内 的正常操作。我们知道,绝大多数网站是通过 cookie 等方式辨识用户身份(包括使用服务 器端 Session 的网站,因为 Session ID 也是大多保存在 cookie 里面的),再予以授权的。 所以要伪造用户的正常操作,最好的方法是通过 XSS 或链接欺骗等途径,让用户在本机(即 拥有身份 cookie 的浏览器端)发起用户所不知道的请求。 就是说,如果用户不老老实实写姓名,写了一个个


