面试爱问一些不是很具体的问题,比较泛:说出一点,或者说的很全面,反映出不同的水平
值类型和引用类型
值类型
let a = 100;
let b = a;
console.log(b)// 100
引用类型
常见的引用类型
let a = { b: 100 };
let c = a;
a.b = 200;
console.log(c.b);// 200
总结
- 值类型 存储到栈中
- 引用类型,(由于json或object存储到栈中会 太大,复制太慢,所以)通过一个堆地址,存储在堆中。需要的时候通过地址从堆查找(所有的计算机程序设计都是这个思想)
常见的值类型
let a;// undefined
const b = 10;// number
const c = 'string';
const d = Symbol('sym');// symbol
const e = true;// boolean
常见的引用类型
const arr = [1, 2, 3];// Array
const obj = { a: 1 };// object
const n = null // 特殊的引用类型,指针指向空地址
// 特殊引用类型,但不用于存储数据,所以没有“拷贝 复制函数”的说法
function fn() {} // 函数类型 可执行
typeof
- 识别所有值类型
- typeof null === 'object'
- typeof undefined === 'undefined'
typeof ${value} !== 'object'可以判断是除null和undefined的基本类型- obj == null: 判断obj是null或undefined
- 识别函数
typeof function () {} === 'function' - 判断是否是引用类型(但是不可以细分)
function deepClone(obj) {
if (typeof obj !== "object" || obj == null) {
return obj;
}
let result;
if (obj instanceof Object) {
result = {};
} else {
result = [];
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
return result;
}
const person = {
name: "zhangsan",
age: 10,
address: {
city: "shanghai",
house: {
number: 1
}
},
arr: ["a", 1, "b"]
};
const Lisi = deepClone(person);
person.name = "lisi";
person.address.city = "guangzhou";
console.log(Lisi.name);
console.log(Lisi.address.city);
instanceof
- 检测
prototype属性是否出现在某个实例对象的原型链上
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
let instanseCar = new Car('make', 'model', 1924);
console.log(instanseCar instanceof Car); // true
console.log(instanseCar instanceof Object); // true
深拷贝的对象
- 值类型
- 引用类型
let objA = {
age: 10,
name: 'zhangsan',
address: {
city: 'shanghai'
},
arr: [1, 'str', 2]
}
TODO:手写深拷贝
思路:
- 值类型或null,直接返回
- 判断是对象或数组
- 遍历对象或数组,注意是自己的属性,而不是原型链的属性,递归
变量计算
字符串拼接
const a = 100 + 10 // 110
const b = 100 + '10' // '10010'
const c = true + '10' // 'true10'
const d = 100 + parseInt('10') // 110
== 运算符转换
100 == '100' // true
0 == '' // true
false == '' // true
0 == false // true
null == undefined // true
// 技巧:除了 == null 之外,其他一律用 ===
// eg:
const obj = { x: 100 }
console.log(obj.a == null) // true,相当于 obj.a === null || obj.a === undefined
if语句和逻辑计算(&& || ! 与或非)
if语句判断的就是truly和falsely变量,而不是true和false
- truly变量: !!a === true
- falsely变量: !!b === false
// 以下是falsely变量,剩下的都是truly变量
!!undefined === false
!!0 === false
!!'' === false
!!false === false
!!NaN === false
!!null === false
原型和原型链
问题
- 如何准确判断一个变量是不是数组: a instanceof Array
- class的原型的本质,怎么理解?
- 手写一个简易的jQuery,考虑插件和扩展性: 学习Class和原型的好方法
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector);
const length = result.length;
for (let i = 0; i < length; i++) {
this[i] = result[i];
}
this.length = length;
this.selector = selector;
}
get(index) {
return this[index];
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i];
fn(elem);
}
}
on(type, fn) {
return this.each((elem) => {
elem.addEventListener(type, fn, false);
});
}
// 扩展很多 DOM API
}
// 插件
jQuery.prototype.dialog = function (info) {
alert(info);
};
// “造轮子”
class myJQuery extends jQuery {
constructor(selector) {
super(selector);
}
// 扩展自己的方法
addClass(className) {}
style(data) {}
}
// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))
ES6中的class
继承
- extends
- super
- 扩展或重写方法
隐式原型和显示原型
通过例子来理解
// 父类
class People {
constructor(name) {
this.name = name;
}
eat() {
console.log(this.name + " eat something");
}
}
// 子类
class Student extends People {
constructor(name) {
super(name);
}
sayHi() {
console.log(this.name + " say hi!!!");
}
}
class Teacher extends People {
constructor(name) {
super(name);
}
}
let xiaoming = new Student("xiaoming");
console.log(xiaoming instanceof Student); // true
// ES6 class的实质是function,可见语法糖
console.log(typeof Student); // "function"
console.log(typeof People); // "function"
// 原型链:实例的隐式原型指向对应的class的prototype
console.log(xiaoming.__proto__ === Student.prototype); // true
console.log(Student.prototype.__proto__ === People.prototype); // true
console.log(JSON.stringify(People.prototype));
console.log(xiaoming.sayHi());
console.log(xiaoming.eat());
原型关系
- 每个class都有一个显示原型prototype
- 每个实例都有隐式原型__proto__
- 实例的__proto__指向对应class的prototype
基于原型执行规则,获取属性或执行方法时
- 先从自身的属性或方法寻找
- 如果找不到,则去__proto__中查找。
- 如果还找不到,就顺着原型链继续寻找,直到Object.prototype.__proto__为null
eg: 比如上图中,xialuo.sayHi()执行时,先在xialuo实例自身中寻找,没有找到,就回去xialuo.__proto__中寻找
理解画出原型链图
作用域和闭包
问题
- 什么是
作用域?什么是自由变量? - 什么是
闭包?闭包的应用场景,作用? this有哪几种赋值情况?应用场景,如何取值?- 手写
bind函数
作用域
- 全局作用域
- 函数作用域
- 块级作用域(ES6新增): let, const 定义的变量有块级作用域,{}内部的区域可以使用
if (true) {
let a = 1;
}
console.log(a);// 报错,在块级作用域外无法读取a的值,a is not defined
自由变量
在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。
- 一个变量在当前作用域没有定义,但是被使用了
- 向上级作用域一层一层的找,直到找到为止
- 如果到全局作用域都没有找到,就会报错: x is not defined
下方闭包的函数作为参数会体现: 所有自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方
闭包
-
闭包是作用域的特殊情况
- 函数作为返回值
function create() { let a = 100; return function() { console.log(a) } } let fn = create() let a = 200; fn();// 结果是多少? // 100- 函数作为参数
function create(fn) { let a = 200; fn(); } let a = 100; let fn = function() { console.log(a) } create(fn); // 结果是100:所有自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方function bar() { console.log(myName); } function foo() { var myName = "极客邦"; bar(); } var myName = "极客时间"; foo(); // 极客时间
说出下面几种情况中 this的取值
- 作为普通函数去调用
- 使用call bind apply
- 作为对象方法被调用
- 在class方法中调用
- 箭头函数
this取什么样的值,是在函数中执行的时候确定的,而不是在定义的时候,与自由变量刚好相反
function fn1() {
console.log(this)
}
fn1() // window
fn1.call({ x: 100})// 直接执行:{ x: 100 }
const fn2 = fn1.bind({ x: 200 })
fn2()// bind只绑定,不执行,所以需要手动执行 { x: 200 }
作用域的相关问题
- this的不同应用场景,如何取值?
- 作为普通函数去调用: this指向window
- 使用call bind apply: 指向绑定者
- 作为对象方法被调用:指向当前对象
- 在class方法中调用:class实例本身
- 箭头函数: 指向上一作用域的值
- 箭头函数表达式的语法比函数表达式更简洁,并且没有自己的
this,arguments,super或new.target。
- 箭头函数表达式的语法比函数表达式更简洁,并且没有自己的
- 手写bind函数
- 实际开发中闭包的应用场景,举例说明
原型链中的this
class People {
constructor(name) {
this.name = name;
}
}
class Student extends People {
constructor(name, number) {
super(name);
this.number = number;
}
sayHi() {
console.log(`姓名 ${this.name} 學號 ${this.number}`);
}
}
const stu1 = new Student("xialuo", "123");
stu1.sayHi();// "姓名 xialuo 學號 123"
stu1.__proto__.sayHi();// "姓名 undefined 學號 undefined"
// obj.__proto__.fun() 的时候this指向了obj.__proto__,这个对象没有name和number
// 实际obj.fun() 执行原理类似obj.__proto__.fun.call(obj) this指向obj
// console.log(stu1);
// console.log(stu1.__proto__);
任务: 闭包的应用场景
1.防抖
// 防抖:连续触发多次,只执行最后一次; 應用場景:input裡連續輸入,button多次點擊
// 节流:连续触发多次,一定时间内只触发一次;應用場景:鼠標移動,讀取鼠標的位置
const fn = () => console.log("fn");
window.onresize = debounce(fn, 500);
function debounce(fn) {
let timer;
return function () {
if (timer) {//timer第一次执行后会保存在内存里 永远都是执行器 直到最后被触发
clearTimeout(timer);
}
timer = setTimeout(() => fn(), 500);
};
}
2.使用闭包设计单例模式
// 单例模式:创建多个实例的时候,只能创建单个实例
class createUser {
constructor(name) {
this.name = name;
}
}
// let a = new createUser("aa");
// console.log(a.name);
// 代理创建单例模式
const ProxyFun = (() => {
let instance = null;
// 通過閉包 訪問閉包通過返回函數來訪問內部變量
return function (name) {
if (!instance) {
instance = new createUser(name);
}
return instance;
};
})(); // 立刻執行,返回一個function
const b = ProxyFun("b");
const c = ProxyFun("c");
console.log(b.name, c.name);
console.log(b === c);
异步
- 问题一: 单线程和异步(同步和异步的区别)?
- js是单线程语言,只能同时做一件事
- 浏览器和nodejs已支持JS启动
进程如Web Worker - JS和DOM渲染共同一个线程,因为JS可修改DOM结构
- 遇到等待(网络请求,定时任务)不可以卡住
- 需要异步解决卡住的问题
- alert是同步,会阻塞代码执行
- setTimeout是异步,不阻塞执行
- 回调callback函数形式
- 问题二:手写用Promise加载一张图片
// 加载图片Fun 成功后和失败后的处理
const loadImage = (url) => {
return new Promise((resolve, reject) => {
let img = document.createElement("img");
img.onload = () => {
resolve(img);
document.body.appendChild(img);
};
img.onerror = () => {
reject(new Error("load image error"));
};
img.src = url;
});
};
const url =
"https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg";
loadImage(url)
.then((data) => {
console.log('width: ', data.width);
return data;
})
.then((data) => {
console.log('height: ', data.height);
})
.catch((error) => console.error(error));
- 问题三:前端使用异步的应用场景
- 网络请求 如ajax 图片加载
- 定时任务 setTimeout、setInteral
相关知识
- 单线程和异步
- 应用场景
- callback hell 和 Promise
event loop
要会画出图,并讲述过程
图组成部分
- Browser console
- Call Stack
- Web APIs(类似setTimeout的时候,存放timer用的)
- Callback Queue(Event Loop)
event Loop(事件轮询/事件循环)流程
- 同步代码,一行一行放在Call Stack执行
- 遇到异步代码,先记录下来,等待时机(定时、网络请求等)
- 时机到了,将异步代码移动到Callback Queue中
- 如Call Stack 为空的时候,Event Loop开始工作
- 轮询查找Callback Queue,如有则移动到Call Stack执行
- 继续轮询查找(永动机一样)
DOM事件和event loop 的关系
$('#btn1').click(function (e) {// 1. 执行到这一行的时候,将callback function暂存到Web APIs里
console.log('click event'); // 2. 当点击的时候,立刻将callback function 提交到Callback Queue中,event loop轮询的时候,将callback function移到call Stack中执行
})
- 异步(setTimeout, ajax等)使用回调,基于event loop
- DOM事件(不是异步)使用回调,基于event loop,
DOM渲染
- 当Call Stack空闲的时候,会尝试渲染DOM,再出发Event Loop、
- JS是单线程的,而且和DOM渲染共用一个线程
例子:coding.imooc.com/lesson/400.…
console.log('Hi');
setTimeout(() => {
console.log('callback1');
}, 5000);
console.log('Bye');
Promise
主要是为了解决回调地狱嵌套(callback hell)的问题
ES6解决异步问题常用方法(语法糖):async await
- 三种状态:Pending、fulfilled(resolved)、rejected
- then正常返回resolved,里面报错则返回rejected
catch正常返回resolved,里面报错则返回rejected- fulfilled(resolved) 触发 then 回调,reject 触发 catch 回调
const p1 = Promise.reject("reject")
.then((data) => {
console.log("then 1:", data);
})
.catch((error) => {
console.log("error 1:", error);
})
.then((data) => {
console.log("then 2:", data);
})
.catch((error) => {
console.log("error 1:", error);
});
console.log('p1: ', p1);// fulfilled
// error 1: reject
// then 2
//
const p2 = Promise.reject('error').catch(err => {
console.error(err)
})
console.log('p2:', p2)// fulfilled
const p3 = Promise.reject('error').catch(err => {
throw new Error('error on catch')
})
console.log('p3:', p3)// rejected
题目
// 题目
// Promise.resolve().then(() => {
// console.log(1)
// }).catch(() => {
// console.log(2)
// }).then(() => {
// console.log(3)
// })
// 1 3
//题目
// Promise.resolve().then(() => {
// console.log(1)
// throw new Error('error')
// }).catch(() => {
// console.log(2)
// }).then(() => {
// console.log(3)
// })
// // 1 2 3
async/await 和 Promise 的关系
- 执行async函数,返回的是Promise对象
- await相当于Promise的then
!(async function() {
const prom = Promise.reject('1')
const res = await prom// 这一行不执行,因为await相当于then,reject的时候不走then
console.log('res', res)// 这一行不会执行,因为在上一行已经报错
})()
async await
- 同步的语法,实现异步
- await需要async包裹
- await后可以是promise、async function
async function async1() {
console.log("async1 start");// 这一行还没有到异步
await async2();
// await后面的都可以看做是callback里面的内容,异步
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(() => {
console.log("setTimeout");
}, 0);
async1();
new Promise((resolve) => {
console.log("promise resolve");
resolve();
}).then(() => {
console.log("promise then");
});
console.log("script end");
// script start
// async1 start
// async2
// promise resolve
// script end
// async1 end
// promise then
// setTimeout
for-of的应用场景
- for...in forEach for是常规的同步遍历
- for...of 用于异步遍历
- 注意区分:网络的同步阻塞,异步HTTP请求
- for...in forEach for同步遍历指的是,多个循环同时执行
- for...of 异步遍历指的是,第一循环执行完,才会执行第二个,不同时即不同步,即异步
function muti(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num * num);
}, 1000);
});
}
const nums = [1, 2, 3];
// nums.forEach(async (i) => {
// const res = await muti(i);
// console.log(res);// 一下子打印
// });
!(async function () {
for (let j of nums) {
const res = await muti(j);
console.log(res); // 异步打印 阻塞打印,等待一秒后打印1,1s后打印4,1s后打印9
// 有结果后再执行下一个
}
})();
微任务microTask 宏任务macroTask
- 宏任务
- setTimeout
- setInterval
- Ajax
- DOM事件
- 微任务
- Promise
- async await
- 微任务比宏任务执行的要早
- 微任务在DOM渲染前触发
- 宏任务在DOM渲染后触发 codepen.io/huangzonggu…
// 1.
// console.log("length:", document.getElementById("container").children.length);
// alert("本次Call stack 結束,DOM结构已更新,但尚未触发渲染");
// 2. 微任务在DOM渲染之前
Promise.resolve().then(() => {
console.log("length:", document.getElementById("container").children.length);
alert("promise then"); // 未渲染,页面看不到
});
//setTimeout(() => {
//console.log("length:", document.getElementById("container").children.length); // 3
//alert("setTimout "); // 已渲染
//});
为什么微任务比宏任务执行的要早:
- 微任务是ES6规定的
- 宏任务是浏览器规定的
- 执行Event Loop的时候,执行到Promise的微任务的时候,先放到micro task queue里面,与Callback Queue分开(宏任务)
console.log(100);
// 宏任务
setTimeout(() => console.log(200), 0);
// 微任务
Promise.resolve().then(() => console.log(300));
console.log(400);
// 100 400 300 200
JS-Web-API
DOM
vue和react封装了DOM的操作
带着问题学习DOM
- DOM是哪种数据结构
- DOM操作的常用API
- attr和property的区别
- 如何一次性插入多个DOM节点,考虑性能
知识点
- DOM的本质
- 树
- DOM节点操作
-
document.createElement(name) -
document.getElementById(id) -
document.getElementsByTagName(name) -
document.getElementsByClassName(className) -
document.getElementsByTagName(tagName) -
document.querySelectorAll:返回一个 NodeList ,IE8+(含)。 -
document.forms:获取当前页面所有form,返回一个 HTMLCollection ;
-
- DOM结构操作
- 新建节点
- createElement('p')
- 插入节点
- appendChild(${新键节点})
- 移动节点
- 对现有的节点进行appendChild(${现有节点})
- 获取子元素节点
- 每个node都包含有nodeType属性。
let NodeListRes = document.getElementById("div1").childNodes; console.log(NodeListRes); // 有七个 // nodeType取值: // 元素节点:1 属性节点:2 文本节点:3 注释节点:8 NodeListRes = Array.prototype.slice .call(NodeListRes) .filter((item) => item.nodeType === 1); console.log("filter result:", NodeListRes); - 获取父元素节点
document.getElementById("p2").parentNode;- 删除节点
document.getElementById("div1").removeChild(document.getElementById("p3")); - 新建节点
- DOM性能
- DOM操作是比较耗费性能的
- 避免频繁查询,查询做缓存
// 频繁查询DOM // for (let i = 0; i < document.getElementsByTagName("p").length; i++) { // console.log(i); // } // 做缓存 一次查询 let tagLength = document.getElementsByTagName("p").length; for (let i = 0; i < tagLength; i++) { console.log(i); }- 避免频繁操作
attr和property的区别
<div id='div1' class='container'>
<p>this is a p tag</p>
<p>this is a p tag</p>
<p>this is a p tag</p>
</div>
let pList = document.querySelectorAll("p");
// set property
pList[0].a = "a";
// set attribute
pList[1].setAttribute("a", "a");
- attribute修改HTML属性,会反映到HTML结构上
- property修改
自定义的JS属性,不会反映到HTML结构上- 需要注意一点,style 是一个比较特殊的例子,修改它的属性,会触发 DOM 样式的改动,会体现到 DOM 中。而我们日常所说的 property,更多是
自定义的属性,例如 p1.a = 100; p1.b = 200 这样子。
- 需要注意一点,style 是一个比较特殊的例子,修改它的属性,会触发 DOM 样式的改动,会体现到 DOM 中。而我们日常所说的 property,更多是
- 修改attribute会导致DOM渲染,修改property有可能导致DOM渲染,但是建议尽量使用property,DOM渲染会耗费性能
BOM (Browser Object Model)
题目
- 如何识别浏览器
- 分拆url的各个部分
知识点
- navigator
- screen
- location
- history
识别浏览器
一般用navigator.userAgent来判断浏览器,但是,这个判断是不严谨的,就好像看到一个鸭子,走路和叫声像鸭子,就判定是鸭子。应该使用各个浏览器的特征来判断
另外,浏览器为了网页能在自己网页中运行,userAgent加了很多标识。
例如,Chrome的userAgent
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36
// Opera 8.0+
var isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
// Firefox 1.0+
var isFirefox = typeof InstallTrigger !== 'undefined';
// Safari 3.0+ "[object HTMLElementConstructor]"
var isSafari = /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification));
// Internet Explorer 6-11
var isIE = /*@cc_on!@*/false || !!document.documentMode;
// Edge 20+
var isEdge = !isIE && !!window.StyleMedia;
// Chrome 1 - 71
var isChrome = !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime);
// Blink engine detection
var isBlink = (isChrome || isOpera) && !!window.CSS;
var output = 'Detecting browsers by ducktyping:<hr>';
output += 'isFirefox: ' + isFirefox + '<br>';
output += 'isChrome: ' + isChrome + '<br>';
output += 'isSafari: ' + isSafari + '<br>';
output += 'isOpera: ' + isOpera + '<br>';
output += 'isIE: ' + isIE + '<br>';
output += 'isEdge: ' + isEdge + '<br>';
output += 'isBlink: ' + isBlink + '<br>';
document.body.innerHTML = output;
参考
Screen(window.screen)
- screen.width
- screen.height
location
console.log('href:', location.href);
console.log('protocol:', location.protocol);
console.log('host:', location.host);
console.log('hostname:', location.hostname);
console.log('hast:', location.hash);
console.log('port:', location.port);
console.log('search:', location.search);
history
- history.back()
- history.forwork()
- history.go(1)
事件
题目
- 编写一个通用的事件绑定函数
- 描述冒泡的过程
- 无线下拉的图片列表,如何监听每个图片点击
知识点
- 事件绑定
- 事件冒泡
- 事件代理
事件绑定
let btnElement = document.getElementById("btn");
// 普通的事件绑定
// btnElement.addEventListener("click", () => alert("alert"));
// 通用的事件绑定(未完善,见下方兼容事件代理的 事件绑定函数)
const myBind = function (type, callback, target) {
target.addEventListener(type, callback);
};
myBind("click", () => alert("myBind"), btnElement);
改造事件绑定
<div id='div1' class='container'>
<p id="p1">点击我 阻止冒泡 </p>
<p>this is a p tag</p>
<p>this is a p tag</p>
<p>this is a p tag</p>
</div>
<div id='div2' class='container'>
<a href="#">a1</a>
<a href="#">a2</a>
<a href="#">a3</a>
<a href="#">a4</a>
<button>i am button</button>
</div>
// 冒泡
// 通用的事件绑定:考虑事件代理
const bindEvent = function (elem, eventType, selector, callback) {
if (callback == null) {
callback = selector;
selector = null;
}
elem.addEventListener(eventType, (event) => {
console.log('event :>> ', event);
if (selector) {
// 事件代理
if (event.target.matches(selector)) {
callback.call(event.target, event)// 调用的时候,this可以指向callback里的function
// callback()
}
} else {
// 普通绑定
callback.call(event.target, event)
// callback()
}
});
};
const bodyElem = document.body;
// const bodyElem = document.getElementById("div1");
bindEvent(bodyElem, "click", function (event) {
alert(this.innerHTML);
});
// 普通绑定
const p1Elem = document.getElementById("p1");
bindEvent(p1Elem, "click", function (event) {
event.stopPropagation();// 阻止冒泡
alert(this.innerHTML);
});
// 事件代理:基于冒泡事件实现
// 子元素太多,逐个绑定事件会很复杂,所以通过事件代理到父元素上
const div2 = document.getElementById('div2');
// 未传selector的时候,自己处理判断是否是触发的目标元素
// bindEvent(div2, 'click', function (event) {
// event.stopPropagation();
// event.preventDefault();// 阻止a标签的跳转: #
// if (event.target.nodeName === 'A') {
// console.log('是a標籤')
// alert(this.innerHTML)
// }
// })
// 传selector, 改造 事件绑定,兼容事件代理
bindEvent(div2, 'click', 'A', function (event) {
event.stopPropagation();
event.preventDefault();// 阻止a标签的跳转: #
// if (event.target.nodeName === 'A') {
// console.log('是a標籤')
alert(this.innerHTML)
// }
})
答:描述事件冒泡的过程
- 基于DOM树形结构
- 事件顺着触发元素向上冒泡
- 应用:事件代理
答:无线下拉的图片列表,如何监听每个图片点击
- 事件代理
- 用event.target来获取触发的元素
- 通过matches来判断 触发元素 是否是图片
AJAX(Asynchronous JavaScript And XML)
AJAX(Asynchronous JavaScript And XML )是一种使用 XMLHttpRequest 技术构建更复杂,动态的网页的编程实践。
目标
- 手写一个简易的AJAX(用Promise)
- 不用具体考虑所有的方法和状态码(重复造轮子)
function myAjax(url) { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(url); xhr.open("GET", url, true); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.code === 200) { resolve(JSON.parse(xhr.responseText)); } else if (xhr.code === 404) { reject(new Error("error")); } } }; xhr.send(null); }); } let res = myAjax("./myAjax.json"); res.then((data) => { console.log("data:", data); }); - 跨域常用的实现方式
知识点
- XMLHttpRequest
XMLHttpRequest(XHR) 是一种创建 AJAX 请求的 JavaScript API 。它的方法提供了在浏览器和服务器之间发送请求的能力。
- 状态码 XMLHttpRequest.status
status码是标准的HTTP status codes XMLHttpRequest.onreadystatechange
XMLHttpRequest.readyState: 4的時候,才可以获取到response
| 分类 | 分类描述 |
|---|---|
| 0 | (未初始化) |
| 1 | (载入) |
| 2 | (载入完成) |
| 3 | (交互) |
| 4 | (完成)响应内容解析完成 |
- 跨域:同源策略,跨域解决方案
- 浏览器的同源策略
- AJAX请求时,浏览器要求当前页面和server必须同源(安全)
- 同源:协议、域名、端口,三者必须一致。一致代表同一来源
- 前端:浏览器浏览 a.com:8080/ api只能是:a.com:8080/api/xxx 如果server不是,则会被浏览器拦截
- 但是服务器访问服务器的数据,是不会拦截的,因为没有经过浏览器,所以出现服务器向服务器发起攻击的安全事件
- 加载图片、css、js不受浏览器的同源策略限制
<img src=跨域的图片地址 />:通过图片实现统计打点,使用第三方统计服务<link href=跨域的css地址 />:CDN<script src=跨域的js地址></script>:CDN 实现JSONP
- 跨域(CORS: Cross-Origin requests 跨域资源共享 )
- 所有的跨域,都必须经过server端允许和配合
- server配置: Access-Control-Allow-Origin(访问控制允许来源)、
- 所有的跨域,都必须经过server端允许和配合
- 解决跨域方案
-
纯服务器
- 服务器 配置http header: Access-Control-Allow-Origin
// 第二个参数填写允许跨域的名称,不建议* response.serHeader("Access-Control-Allow-Origin", "http://localhost:8011"); response.serHeader("Access-Control-Allow-Headers", "X-Requested-With"); response.serHeader("Access-Control-Allow-Origin", "PUT,POST,GET,DELETE,OPTIONS"); response.serHeader("Access-Control-Allow-Credentials", "true"); -
需要服务端配合
- JSONP:JSON with Padding (填充式 JSON) (不推荐使用)
-
实现方式
- 通过script获取数据,返回
// 后台处理返回 callback({ name: 'zhangsan', age: 12 })- 前端处理script(跨域的URL)请求数据
window.callback = function(data) { console.log(data)// 成功取到跨域的数据 // { // name: 'zhangsan', // age: 12 // } } -
缺点
- 只支持GET
- 安全性不够
- JSONP暴露了很多漏洞,它假定了传送回来的代码是可信的,进一步暴露了CSRF(跨站点请求伪造)漏洞。
- 没有错误处理,调试困难
-
- JSONP:JSON with Padding (填充式 JSON) (不推荐使用)
-
纯前端
- 代理
-
- 浏览器的同源策略
思考🤔
- 是不是可以通过跨域的方法,谁都可以做一个山寨淘宝出来?如果是这样,那么跨域好像就没有什么意义了吧。虽然有同源策略限制,但是大家都知道解决这个限制的方法,那就相当于没有限制?
AJAX的一些插件
- jQuery
- Fetch
- 注意: 当接收到一个代表错误的 HTTP 状态码时,从
fetch()返回的 Promise 不会被标记为 reject, 即使响应的 HTTP 状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的ok属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。
- 注意: 当接收到一个代表错误的 HTTP 状态码时,从
- axios
存储
Cookie
- 特点
- 存储大小,最大4k
- 随着http请求,
- 用途
- 会话状态管理(如用户登录信息、购物车、游戏分数或其他需要记录的信息)
- 个性化设置(如用户的个性化设置,主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)
- 观点
- 现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。由于服务器指定 Cookie 后,浏览器的每次请求都会携带 Cookie 数据,会带来额外的性能开销(尤其是在移动环境下)。新的浏览器API已经允许开发者直接将数据存储到本地,如使用 Web storage API (本地存储和会话存储)或 IndexedDB 。
- 安全
- JavaScript 通过 Document.cookie 访问 Cookie
-
JavaScript可以通过跨站脚本(xss)来窃取cookie,
-
维基百科已经给了一个比较好的 CSRF 例子。比如在不安全聊天室或论坛上的一张图片,它实际上是一个给你银行服务器发送提现的请求:
<img src="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">-
当你打开含有了这张图片的 HTML 页面时,如果你之前已经登录了你的银行帐号并且 Cookie 仍然有效(还没有其它验证步骤),你银行里的钱很可能会被自动转走。有一些方法可以阻止此类事件的发生:
- 对用户输入进行过滤来阻止 XSS (en-US);
- 任何敏感操作都需要确认;
- 用于敏感信息的 Cookie 只能拥有较短的生命周期;
- 更多方法可以查看OWASP CSRF prevention cheat sheet。
-
-
- JavaScript 通过 Document.cookie 访问 Cookie
localStorage和sessionStorage
- 特点
- HTML5专门为存储而设计,大小限制为5M
- API易用:getItem('key', 'value')、setItem('key')、removeItem('key')、clear()
- 不会随着http请求发送出去
- 区别
- localStorage会持久化存储,除非代码删除或手动删除
- sessionStorage 关闭对应浏览器窗口(Window)/ tab,会清除对应的
sessionStorage。
TODO: 拓展思考🤔
- 不用cookie的话,用什么方式实现登录信息的保存?这个方式如何避免cookie的安全问题?
- token登录的过程是如何实现的?
- 如何持久化保存用户的登录信息?
常见正则
掌握基本的场景正则,手写
- 用户名(判断字符串已字母开头,后面字母数字下划线,长度6-30)
- 邮件
- 小写英文字母
- 英文字母
- 邮政编码
- 简单的ip地址
- 日期格式(2021.09.11)
正则学习网站:deerchao.cn/tutorials/r…
思考🤔(题目)
Object.create() 和 new Object()的区别
new Object 和 {} 新键对象相同,原型都是Object.prototype
Object.create()使用现有的对象来提供新键对象的__proto__
let obj = {
a: 100
};
let objectCreateObj = Object.create(null);
console.log(objectCreateObj.__proto__); // undefined
let objectCreateWithObj = Object.create(obj);
console.log(objectCreateWithObj.a); // 100
console.log(objectCreateWithObj.__proto__ === obj); //true
JS如何实现继承
- class继承(ES6引入class)
class Animal {
constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
}
say () {
return 'Hello, ' + this.name + '!'
}
}
// 测试:
var kitty = new Cat('Kitty');
var doraemon = new Cat('哆啦A梦');
if ((new Cat('x') instanceof Animal)
&& kitty
&& kitty.name === 'Kitty'
&& kitty.say
&& typeof kitty.say === 'function'
&& kitty.say() === 'Hello, Kitty!'
&& kitty.say === doraemon.say)
{
console.log('测试通过!');
} else {
console.log('测试失败!');
}
参考 www.liaoxuefeng.com/wiki/102291…
- prototype继承
function Person(first, last, age, gender) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
}
function Teacher(first, last, age, gender, subject) {
Person.call(this, first, last, age, gender);
this.subject = subject;
}
let teacher1 = new Teacher("aki", "huang", 20, "男", "王牌讲师");
console.log(teacher1);
参考资料 developer.mozilla.org/zh-CN/docs/…
参考