new
运算符的优先级function Foo() {
return this;
}
Foo.getName = function () {
console.log('1');
};
Foo.prototype.getName = function () {
console.log('2');
};
new Foo.getName(); // -> 1
new Foo().getName(); // -> 2
通过这段代码可以看出:new Foo()
的优先级高于 new Foo
.
对于代码1 来说:是将 Foo.getName
当成一个构造函数来执行,执行构造函数所以输出为1.
对于代码2来说:通过 new Foo()
创建了一个Foo的实例,通过实例访问其原型链上的 方法所以输出为2.
var foo = 1;
// 有名立即执行函数
(function foo(){
foo = 1;
console.log(foo);
})();
// 执行这段代码会输出什么呢?
// -> ƒ foo() { foo = 10 ; console.log(foo) }
// 再去访问 foo 的值
foo
// -> 1
当JS执行器遇到非匿名的立即执行函数时,会创建一个辅助的特定对象,然后将函数名称作为这个对象的属性,因此行数内部才可以访问到 foo
,但这个值是只读的,所以对其重新赋值不会生效,所以打印结果还是这个函数,并且外部的值也没有发生改变。
可以使用 JSON.stringify
和 JSON.parse
这个两个方法
优点:简单
缺点:会忽略掉 undefined
; 不能序列化函数 ; 不能解决循环引用的对象
function clone(obj) {
return JSON.parse(JSON.stringify(obj));
}
使用递归循环赋值的方式
优点:可以处理 undefined
、函数等各种情况
缺点:实现相对麻烦,效率不高
function clone(obj) {
if(!obj || typeof obj !== 'object') {
return;
}
var _obj = obj.constructor === Object ? {} : [];
for(let key in obj) {
if(typeof obj[key] === 'object') {
_obj[key] = clone(obj[key]);
} else {
_obj[key] = obj[key];
}
}
return _obj;
}
// 或者
function clone(obj) {
if(!obj || typeof obj !== 'object')
throw new TypeError('params typeError');
let _obj = obj.constructor === Object ? {} : [];
Object.getOwnPropertyNames(obj).forEach(name => {
if(typeof obj[name] === 'object') {
_obj[name] = clone(obj[name]);
} else {
_obj[name] = obj[name];
}
});
return _obj;
}
使用内置 MessageChannel
对象
优点:是内置函数中处理深拷贝性能最快的
缺点:不能处理函数(会报错)
function clone(obj) {
return new Promise(resolve => {
let {port1, port2} = new MessageChannel();
port2.onmessage = ev => resolve(ev.data);
port1.postMessage(obj);
});
}
clone(obj).then(console.log);
async/await
, promise
异步执行顺序想解决这个问题,就必须知道 `await` 做了什么?
刚开始以为await
会一直等待表达执行的执行结果,之后才会执行后面的代码。 实际上await
是一个让出线程的标志(遇到await
会立即返回一个padding
状态的promise)。await后面的函数会先执行一遍,然后就会跳出整个async
函数来执行后面js代码。等本轮事件循环执行完又跳回到async
函数中等待await后面表达式的返回值,如果返回值为非promise
则继续执行async后面的代码,否则将promse
加入队列。
且看一道面试题(分析代码执行 顺序):
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function () {
console.log("settimeout");
},0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log("script end");
OK,那接下来具体分析执行过程:
首先输出 "script start" ,然后立即将定时器加入异步事件队列。执行 async1() ,输出 "async1 start" ,进入 async2() ,输出 "async2" ,跳出整个 async1() 函数来执行后面js代码,执行promise执行器中的内容,输出 "promise1" ,执行resolve()回调,将then函数中的内容加入异步事件队列,接着输出 "script end" 。回到 async1() 的await等待 async2() 函数的返回值,因为返回值是一个promise实例,将promise加入异步事件队列。此时的同步代码执行完毕,轮询并从队列拿出代码放入主线程执行,所以输出 "promise2" ,继续执行 async1() 中的后续内容,输出 "async1 end" ,最后取出定时器中的内容并执行,输出 "settimeout" 。
综上所述:
script start
async1 start
async2
promise1
script end
promise2
async1 end
settimeout
那么再看一个例子应该会简单很多:
function testFunc() {
console.log("testFunc..."); // 2
return "testFunc";
}
async function testAsync() {
console.log("testAsync..."); // 7
return Promise.resolve("hello testAsync");
}
async function foo() {
console.log("test start..."); // 1
const v1 = await testFunc();
connsole.log('hello world.'); // 5
console.log(v1); // 6 testFunc
const v2 = await testAsync();
console.log(v2); // 9 hello testAsync
}
foo();
var promise = new Promise(resolve => {
console.log("promise start.."); // 3
resolve("promise");
});
promise.then(val => console.log(val)); // 8 promise
console.log("test end..."); // 4
一个简单的防抖函数:
function debounce(func, wait) {
let timer = null;
return function(...params) {
// 如果定时器存在则清除
if(timer){
clearTimeout(timer);
}
// 重新开始定时执行
timer = setTimeout(() => {
func.apply(this, params);
}, wait);
}
}
缺点:只能在最后执行,不能立即被执行,在某些情况下不适用。
改进...
function debounce(func, wait, immediate) {
let timer, context, args;
// 定时器
let later = function() {
return setTimeout(() => {
timer = null;
if(!immediate) {
func.apply(context, args);
}
}, wait);
}
return function(...params) {
if(!timer) {
timer = later();
// immediate 为 true,则立即执行
// 否则 缓存上下文 和 参数
if(immediate) {
func.apply(this, params);
} else {
context = this;
args = params;
}
} else {
clearTimeout(timer);
timer = later();
}
}
}
一个简单的节流函数:
// 节流函数
// 快速的多次执行,转化为等待wait时间再去执行
function throttle(func, wait) {
var timer = null;
var context = null;
return function(...args) {
context = this;
if(!timer) {
timer = setTimeout(function() {
timer = null;
func.apply(context, args);
}, wait);
}
}
}
// 如果想让第一次调用立即执行也非常简单
仅需要将 func.apply(context, args) 提到定时器外边即可。
节流函数除了可以使用定时器实现以外,当然也可以有其他方式:
// 第一次调用会被立即执行
function throttle(func, wait) {
var prev = 0;
var context = null;
return function(...args) {
var now = +new Date();
context = this;
if(now -prev > wait) {
func.apply(context,args);
prev = now;
}
}
}
怎么去模拟一个call函数呢?
思路:call一个非常重要的作用就是改变上下文环境也就是this,我们可以给用户传入的上下文对象上添加一个函数,通过这个上下文对象去执行函数,然后将这个函数删除,返回结果就可以了。
Function.prototype.myCall = function(context, ...args) {
context = context || window;
// 给上下文对象上添加这个函数
context.fn = this;
// 通过这个上下文对象去执行函数
let result = context.fn(...args);
// 将这个函数删除
delete context.fn;
return result;
}
call既然都实现了,那么apply也是类似的,只不过传入的参数是一个数组而已。
Function.prototype.myApply = function(context, arr) {
context = context || window;
arr = arr || [];
let type = {}.toString.call(arr).slice(8,-1);
if(type !== 'Array')
throw new TypeError('CreateListFromArrayLike called on non-object');
context.fn = this;
let result = context.fn(...arr);
delete context.fn;
return result;
}
模拟bind函数,bind函数应该返回一个新的函数。
Function.prototype.myBind = function(context, ...args) {
// 保存当前的函数
let func = this;
return function F(...params) {
if(this instanceof F) {
return new func(...args, ...params);
}
return func.apply(context,[...args,...params]);
}
}
function flattenDeep(arr) {
if(!Array.isArray(arr))
return [arr];
return arr.reduce((prev,cur) => {
return [...prev, ...flattenDeep(cur)];
},[]);
}
flattenDeep([1, [[2], [3, [4]], 5]]);
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。
function IsPopOrder(pushV,popV){
if(pushV.length === 0) return false;
var stack = []; // 模拟栈
for(var i = 0, j = 0; i < pushV.length;){
stack.push(pushV[i]);
i += 1;
// 压入栈的值需要被弹出
while(j < popV.length && stack[stack.length-1] === popV[j]){
stack.pop();
j++;
if(stack.length === 0) break;
}
}
return stack.length === 0;
}
思路:
var stackA = [];
var stackB = [];
function push(node){
stackA.push(node);
}
function pop(){
if(!stackB.length){
while(stackA.length){
stackB.push(stackA.pop());
}
}
return stackB.pop();
}
fetch
fetch(url).then(function(response){
return response.json();
}).then(function(data){
console.log(data);
}).catch(function(err){
console.log(err);
});
ajax
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function(){
console.log(xhr.response);
}
xhr.onerror = function(){
console.log('error');
}
xhr.send();
fetch(url, {credentials: 'include'})
将一个完整的数组分成两部分,分别对其排序,然后将两部分merge在一起即可。
function merge(left, right) {
var temp = [];
while(left.length && right.length) {
if(left[0] < right[0])
temp.push(left.shift());
else
temp.push(right.shift());
}
return temp.concat(left,right);
}
function mergeSort(arr) {
if(arr.length === 1)
return arr;
var mid = (arr.length/2)|0;
var left = arr.slice(0,mid);
var right = arr.slice(mid);
return merge(mergeSort(left), mergeSort(right));
}
this
指向固定化,并不是因为箭头函数内部有绑定 this
的机制,实际原因是箭头函数没有自己的 this
,导致内部的this就是外层代码的 this
,也正是因为没有 this
,所以箭头函数不能用作构造函数。
浮动元素参与计算
var content = document.querySelector('.content');
// 自定义事件
var evt = new Event('custom');
var customEvt = new CustomEvent('customEvt', {
// 通过这个属性传递参数
detail: {
name: 'tom',
age: 12
}
});
content.addEventListener('custom', (e) => {
console.log('自定义事件被触发,无参数...');
console.log(e);
});
content.addEventListener('customEvt', (e) => {
console.log('自定义事件被触发,有参数...');
console.log(e);
console.log(e.detail);
});
// 点击时触发这个自定义事件
content.addEventListener('click', (e) => {
content.dispatchEvent(evt);
content.dispatchEvent(customEvt);
});
var foo = 3; // 不在同一个作用域
function hoistVariable() {
// 内部变量提升导致 foo 的初始值为undefined
// 所以 foo = 5;
var foo = foo || 5;
console.log(foo); // 5
}
hoistVariable();
上边的比较简单,看一个函数和变量同名,关于变量提升的小问题。
var a = 6;
function b(){
console.log(a); // @1
var a = 4;
function a(){
alert(4);
}
console.log(a); //@2
}
b();
@1
输出的是 function a(){alert(4);}
a=4;
这一句,重新对 a
进行赋值。@2
这里自然会输出 4
。如果还不能理解?且看预编译后的代码:
var a;
a = 6;
function b(){
var a;
a = function a(){ // 函数先提升
alert(4);
}
console.log(a); // @1
a = 4;
console.log(a); // @2
}
b(); // 结果已经非常明了了
1. 前后端如何通信?
2. 如何创建ajax?
// 创建xhr对象
var xhr = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP');
// GET 请求
xhr.open('GET',url,true);
xhr.send();
// POST 请求
xhr.open('POST',url,true);
// 表单数据 , 也可以提交json数据,相应的content-Type: application/json
xhr.setRequestHeader('content-Type', 'application/x-www-from-urlencoded');
xhr.send(dataArr.join('&'));
xhr.onload = function() {
if(xhr.status === 200 || xhr.status === 304) {
var data = xhr.responseText;
// 拿到数据
} else {
// 出问题了
}
}
3. 跨域通信的几种方式?
JSONP:利用 script
标签的跨域能力,服务返回一个js函数调用,数据作为函数的一个参数来传递。
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url; // 跨域地址
document.head.appendChild(script);
//有能耐把我这辈子都安排了,不然少他妈扯淡。
setTimeout(function() {
document.head.removeChild(script);
script = null;
});
// 接收数据
function jsonpCallback(data) {
console.log(data);
}
WebSocket:不受同源政策限制。
var ws = new WebSocket('wss://echo.websocket.org');
ws.onopen = function(e) {
ws.send('hello...');
}
ws.onmessage = function(e) {
var data = e.data;
}
ws.onclose = function() {
console.log('close...');
}
Hash:利用 location.hash
来传值。 缺点:数据直接暴露在url中,大小、类型都有限制。
1、父窗体可以把信息写在子窗体的href的hash上,子窗口通过监听hashchange事件获取信息。
2、子窗体改变父窗体的hash值,那么就要借助第三个子窗体,第三个子窗体是第二个子窗体的子窗体。(第三个子窗体要与父窗体同源)
3、第二个子窗体把信息设置在第三个子窗体的hash值上,然后第三个子窗体改变父窗体的hash值,从而实现跨域。
// 父窗体
var son = document.getElementByTagName('iframe');
son.src = son.src + '#' + data;
// 子窗体
window.onhashchange = function() {
var data = window.location.hash;
}
postMessage :语法:window.postMessage(msg,targetOrigin)
// 窗口A 发送
BWindow.postMessage('发送的数据', 'http://B.com');
// 窗口B 接收
window.addEventListener('message', (event) => {
event.origin: // http://A.com
event.source; // AWindow
event.data; // '发送的数据'
});
CORS: 跨域资源共享。
fetch(url, {
method: 'get',
// 头信息配置
}).then(() => {});
防御措施:
Token验证(请求必须携带Token)
Referer 验证(验证请求的来源是否可信)
原理:注入脚本
防御措施:对用户的输入做验证
1. 什么是DOCTYPE及作用?
用来声明文档类型和DTD规范的。
DTD(document type definition)文档类型定义,是一系列的语法规则,用来声明XML或(X)HTML的文件类型,浏览器会使用它来决定文档类型,决定使用何种协议来解析,以及切换浏览器模式。
2. 常见的doctype有哪些?
提升页面性能的方式?
非核心代码异步加载
异步加载方式:
预解析DNS
运行错误捕获:(1)try...catch (2)window.onerror
资源加载错误 :(1)object.onerror(资源错误不冒泡) (2)performance.getEntries() (3)Error事件捕获(在事件流捕获阶段处理错误)
上报错误原理
利用Image对象上报
// 利用Image标签上报错(简单、不需要借助其他库)
(new Image()).src = 'http://www.baidu.com/test?error=xxx';
function binarySearch(arr,val,leftIndex,rightIndex) {
if(leftIndex > rightIndex){ return; }
var midIndex = (leftIndex + rightIndex) / 2 | 0;
var midVal = arr[midIndex];
if(val > midVal) {
return binarySearch(arr,val,midIndex+1,rightIndex);
}else if(val < midVal) {
return binarySearch(arr,val,leftIndex,midIndex-1);
}else{
return midIndex;
}
}
在一个字符串中找出连续的不重复的最大长度的字符串,解决这类问题的思路:
// 连续最长不重复字符串
function getMaxLenStr(str) {
var cur = [];
var maxLenStr = '';
for(var i = 0; i < str.length; i++) {
if(!cur.includes(str[i])) {
cur.push(str[i]);
} else {
cur = []; // 置为空
cur.push(str[i]);
}
// 存储最大长度的字符串
if(maxLenStr.length < cur.length) {
maxLenStr = cur.join('');
}
}
return maxLenStr;
}
getMaxLenStr('ababcabcde'); // abcde
和上面这道题有一样思路的是:求一个数组当中,连续子向量的最大和。
无非是将 对比字符串的长度 改为 对比值大小
function FindGreatestSumOfSubArray(arr) {
let sum = arr[0];
let max = arr[0];
for(let i = 1; i < arr.length; i++) {
if(sum < 0) {
sum = arr[i];
}else{
sum += arr[i];
}
// 记录最大值
if(max < sum) {
max = sum;
}
}
return max;
}
阿里的一道面试题:给定一个编码字符,按编码规则进行解码,输出字符串
编码规则:coount[letter]
,将letter
的内容count
次输出,count
是0或正整数,letter
是区分大小写的纯字母。
实例:
3[a]2[bc]
; decodeString(s); // 返回 ‘aaabcbc’
3[a2[c]]
; decodeString(s); // 返回 ‘accaccacc’
2[ab]3[cd]ef
; decodeString(s); // 返回 ‘ababcdcdcdef’
解题过程...
使用栈这种数据结构,如果push
的内容为‘]’
,则循环pop
字符,直到碰到’[‘
,然后将pop
出来的字符串按规则整理后,重新push
进栈中,最后将栈内的内容拼接成字符串输出即可。
代码:
const s = '2[a2[c]]ef';
function decodeString(str) {
let stack = []; // 存储字符串的栈
for (let i = 0; i < str.length; i++) {
let cur = str[i];
if (cur !== ']') {
stack.push(cur);
} else { // 弹出
let count = 0;
let loopStr = [];
let popStr = '';
while ((popStr = stack.pop()) !== '[') {
loopStr.unshift(popStr);
}
count = stack.pop();
// 添加结果
let item = '';
for (let i = 0; i < count; i++) {
item += loopStr.join('');
}
stack.push(...(item.split('')));
}
}
return stack.join('');
}
console.log(decodeString(s)); // accaccef
移动次数
与关键字的初始排列次序无关的是:基数排列。比较次数
与初始序列无关是:选择排序。时间复杂度
与初始序列无关的是:选择排序。BOM 即浏览器对象模型,BOM没有相关标准,BOM的核心对象是window对象。
DOM即文档对象模型,DOM是W3C标准,DOM的最根本对象是document(window.document), 这个对象实际上是window对象的属性,这个对象的独特之处是唯一一个既属于BOM有属于DOM的对象。
都是建立于tcp连接之上,通过tcp协议来传输数据。
HTTP是一种单向的协议,即客户端只能向服务器端请求信息。request永远等于response,并且这个response是被动的,不能主动发起。一旦有一个任务超时,就会阻塞后续的任务(线头阻塞)。
HTTP协议是无状态的,如使用轮询、long poll都需要将身份鉴别信息上传。
WebSocket真正的全双工通信,只需要一次连接,这样就避免了HTTP的无状态性,服务器端可以主动推送消息到达客户端。
function unique(arr) {
let obj = {};
let ret = [];
for(let i = 0; i < arr.length; i++) {
let cur = `${typeof arr[i]}-${arr[i]}`;
if(!obj[cur]) {
ret.push(arr[i]);
obj[cur] = true;
}
}
return ret;
}
var arr = [1, '1', 3, 3, 4];
unique(arr);
// [1, '1', 3, 4]
function isSame(a, b) {
if(a === b) return true;
if(typeof a !== typeof b) return false;
// 如果均为object类型,判断属性个数是否相同
if(typeof a === 'object' && typeof b === 'object') {
let aLen = Object.getOwnPropertyNames(a).length;
let bLen = Object.getOwnPropertyNames(b).length;
if(aLen !== bLen) return false;
}
return Object.getOwnPropertyNames(a).every(key => {
if(typeof a[key] === 'object') {
return isSame(a[key], b[key]);
}
return a[key] === b[key];
});
}