两者对比:强类型语言在速度上可能略逊色于弱类型语言,但是强类型语言带来的严谨性可以有效地帮助避免许多错误。
值得注意的是,和⼤多数浏览器不同,Chrome 浏览器的每个标签⻚都分别对应⼀个呈现引擎实例。每个标签⻚都是⼀个独⽴的进程。
(1)管道通信
管道是一种最基本的进程间通信机制。管道就是操作系统在内核中开辟的一段缓冲区,进程1可以将需要交互的数据拷贝到这段缓冲区,进程2就可以读取了。
管道的特点:
(2)消息队列通信
消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
使用消息队列进行进程间通信,可能会收到数据块最大长度的限制约束等,这也是这种通信方式的缺点。如果频繁的发生进程间的通信行为,那么进程需要频繁地读取队列中的数据到内存,相当于间接地从一个进程拷贝到另一个进程,这需要花费时间。
(3)信号量通信
共享内存最大的问题就是多进程竞争内存的问题,就像类似于线程安全问题。我们可以使用信号量来解决这个问题。信号量的本质就是一个计数器,用来实现进程之间的互斥与同步。例如信号量的初始值是 1,然后 a 进程来访问内存1的时候,我们就把信号量的值设为 0,然后进程b 也要来访问内存1的时候,看到信号量的值为 0 就知道已经有进程在访问内存1了,这个时候进程 b 就会访问不了内存1。所以说,信号量也是进程之间的一种通信方式。
(4)信号通信
信号(Signals )是Unix系统中使用的最古老的进程间通信的方法之一。操作系统通过信号来通知进程系统中发生了某种预先规定好的事件(一组事件中的一个),它也是用户进程之间通信和同步的一种原始机制。
(5)共享内存通信
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问(使多个进程可以访问同一块内存空间)。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
(6)套接字通信
上面说的共享内存、管道、信号量、消息队列,他们都是多个进程在一台主机之间的通信,那两个相隔几千里的进程能够进行通信吗?答是必须的,这个时候 Socket 这家伙就派上用场了,例如我们平时通过浏览器发起一个 http 请求,然后服务器给你返回对应的数据,这种就是采用 Socket 的通信方式了。
CSRF 攻击可以使用以下方法来防护:
参考:前端进阶面试题详细解答
module.exports={
//入口文件的配置项
entry:{},
//出口文件的配置项
output:{},
//模块:例如解读CSS,图片如何转换,压缩
module:{},
//插件,用于生产模版和各项功能
plugins:[],
//配置webpack开发服务功能
devServer:{}
}
简单描述了一下这几个属性是干什么的。
描述一下npm run dev / npm run build执行的是哪些文件
通过配置proxyTable来达到开发环境跨域的问题,然后又可以扩展和他聊聊跨域的产生,如何跨域
最后可以在聊聊webpack的优化,例如babel-loader的优化,gzip压缩等等
首先判断数据类型是否为对象,如果是对象(数组|对象),则递归(深/浅拷贝),否则直接拷贝。
function isObject(obj) {
return typeof obj === "object" && obj !== null;
}
这个函数只能判断 obj
是否是对象,无法判断其具体是数组还是对象。
localStorage:以键值对的方式存储 储存时间没有限制 永久生效 除非自己删除记录
sessionStorage:当页面关闭后被清理与其他相比不能同源窗口共享 是会话级别的存储方式
cookies 数据不能超过4k 同时因为每次http请求都会携带cookie 所有cookie只适合保存很小的数据 如会话标识
const getJSON = function(url) {
return new Promise((resolve, reject) => {
const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
xhr.open('GET', url, false);
xhr.setRequestHeader('Accept', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4) return;
if (xhr.status === 200 || xhr.status === 304) {
resolve(xhr.responseText);
} else {
reject(new Error(xhr.responseText));
}
}
xhr.send();
})
}
forEach
Array.prototype.forEach2 = function(callback, thisArg) {
if (this == null) {
throw new TypeError('this is null or not defined')
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function')
}
const O = Object(this) // this 就是当前的数组
const len = O.length >>> 0 // 后面有解释
let k = 0
while (k < len) {
if (k in O) {
callback.call(thisArg, O[k], k, O);
}
k++;
}
}
O.length >>> 0 是什么操作?就是无符号右移 0 位,那有什么意义嘛?就是为了保证转换后的值为正整数。其实底层做了 2 层转换,第一是非 number 转成 number 类型,第二是将 number 转成 Uint32 类型
map
基于 forEach 的实现能够很容易写出 map 的实现:
- Array.prototype.forEach2 = function(callback, thisArg) {
+ Array.prototype.map2 = function(callback, thisArg) {
if (this == null) {
throw new TypeError('this is null or not defined')
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function')
}
const O = Object(this)
const len = O.length >>> 0
- let k = 0
+ let k = 0, res = []
while (k < len) {
if (k in O) {
- callback.call(thisArg, O[k], k, O);
+ res[k] = callback.call(thisArg, O[k], k, O);
}
k++;
}
+ return res
}
filter
同样,基于 forEach 的实现能够很容易写出 filter 的实现:
- Array.prototype.forEach2 = function(callback, thisArg) {
+ Array.prototype.filter2 = function(callback, thisArg) {
if (this == null) {
throw new TypeError('this is null or not defined')
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function')
}
const O = Object(this)
const len = O.length >>> 0
- let k = 0
+ let k = 0, res = []
while (k < len) {
if (k in O) {
- callback.call(thisArg, O[k], k, O);
+ if (callback.call(thisArg, O[k], k, O)) {
+ res.push(O[k])
+ }
}
k++;
}
+ return res
}
some
同样,基于 forEach 的实现能够很容易写出 some 的实现:
- Array.prototype.forEach2 = function(callback, thisArg) {
+ Array.prototype.some2 = function(callback, thisArg) {
if (this == null) {
throw new TypeError('this is null or not defined')
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function')
}
const O = Object(this)
const len = O.length >>> 0
let k = 0
while (k < len) {
if (k in O) {
- callback.call(thisArg, O[k], k, O);
+ if (callback.call(thisArg, O[k], k, O)) {
+ return true
+ }
}
k++;
}
+ return false
}
reduce
Array.prototype.reduce2 = function(callback, initialValue) {
if (this == null) {
throw new TypeError('this is null or not defined')
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function')
}
const O = Object(this)
const len = O.length >>> 0
let k = 0, acc
if (arguments.length > 1) {
acc = initialValue
} else {
// 没传入初始值的时候,取数组中第一个非 empty 的值为初始值
while (k < len && !(k in O)) {
k++
}
if (k > len) {
throw new TypeError( 'Reduce of empty array with no initial value' );
}
acc = O[k++]
}
while (k < len) {
if (k in O) {
acc = callback(acc, O[k], k, O)
}
k++
}
return acc
}
1、意外的全局变量:由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收
2、被遗忘的计时器或回调函数:设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。
3、脱离 DOM 的引用:获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。
4、闭包:不合理的使用闭包,从而导致某些变量一直被留在内存当中。
(function(){
var x = y = 1;
})();
var z;
console.log(y); // 1
console.log(z); // undefined
console.log(x); // Uncaught ReferenceError: x is not defined
这段代码的关键在于:var x = y = 1; 实际上这里是从右往左执行的,首先执行y = 1, 因为y没有使用var声明,所以它是一个全局变量,然后第二步是将y赋值给x,讲一个全局变量赋值给了一个局部变量,最终,x是一个局部变量,y是一个全局变量,所以打印x是报错。
function a() {
var temp = 10;
function b() {
console.log(temp); // 10
}
b();
}
a();
function a() {
var temp = 10;
b();
}
function b() {
console.log(temp); // 报错 Uncaught ReferenceError: temp is not defined
}
a();
在上面的两段代码中,第一段是可以正常输出,这个应该没啥问题,关键在于第二段代码,它会报错Uncaught ReferenceError: temp is not defined。这时因为在b方法执行时,temp 的值为undefined。
描述:所有 promise
的状态都变成 fulfilled
,就会返回一个状态为 fulfilled
的数组(所有promise
的 value
)。只要有一个失败,就返回第一个状态为 rejected
的 promise
实例的 reason
。
实现:
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
if(Array.isArray(promises)) {
if(promises.length === 0) return resolve(promises);
let result = [];
let count = 0;
promises.forEach((item, index) => {
Promise.resolve(item).then(
value => {
count++;
result[index] = value;
if(count === promises.length) resolve(result);
},
reason => reject(reason)
);
})
}
else return reject(new TypeError("Argument is not iterable"));
});
}
// 我这里只是一个示例
const d = {};
let ary = ['赵', '钱', '孙', '孙', '李', '周', '李', '周', '周', '李'];
ary.forEach(k => !d[k] ? d[k] = 1 : d[k]++);
const result = Object.keys(d).sort((a, b) => d[b] - d[a]).filter((k, i, l) => d[k] === d[l[0]]);
console.log(result)
function Parent() {
this.a = 1;
this.b = [1, 2, this.a];
this.c = { demo: 5 };
this.show = function () {
console.log(this.a , this.b , this.c.demo );
}
}
function Child() {
this.a = 2;
this.change = function () {
this.b.push(this.a);
this.a = this.b.length;
this.c.demo = this.a++;
}
}
Child.prototype = new Parent();
var parent = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.a = 11;
child2.a = 12;
parent.show();
child1.show();
child2.show();
child1.change();
child2.change();
parent.show();
child1.show();
child2.show();
输出结果:
parent.show(); // 1 [1,2,1] 5
child1.show(); // 11 [1,2,1] 5
child2.show(); // 12 [1,2,1] 5
parent.show(); // 1 [1,2,1] 5
child1.show(); // 5 [1,2,1,11,12] 5
child2.show(); // 6 [1,2,1,11,12] 5
这道题目值得神帝,他涉及到的知识点很多,例如this的指向、原型、原型链、类的继承、数据类型等。
解析:
Child
的构造函数原本是指向Child
的,题目显式将Child
类的原型对象指向了Parent
类的一个实例,需要注意Child.prototype
指向的是Parent
的实例parent
,而不是指向Parent
这个类。parent
是一个Parent
类的实例,Child.prorotype
指向的是Parent
类的另一个实例,两者在堆内存中互不影响,所以上述操作不影响parent
实例,所以输出结果不变;child1
执行了change()
方法后,发生了怎样的变化呢?Child.prototype
上的b数组,this.a会指向child1
的a属性,所以Child.prototype.b
变成了**[1,2,1,11]**;this.a
和this.b
的指向与上一句一致,故结果为child1.a
变为4;child1
自身属性并没有c这个属性,所以此处的this.c
会指向Child.prototype.c
,this.a
值为4,为原始类型,故赋值操作时会直接赋值,Child.prototype.c.demo
的结果为4,而this.a
随后自增为5(4 + 1 = 5)。child2
执行了change()
方法, 而child2
和child1
均是Child
类的实例,所以他们的原型链指向同一个原型对象Child.prototype
,也就是同一个parent
实例,所以child2.change()
中所有影响到原型对象的语句都会影响child1
的最终输出结果。Child.prototype
上的b数组,this.a会指向child2
的a属性,所以Child.prototype.b
变成了**[1,2,1,11,12]**;this.a
和this.b
的指向与上一句一致,故结果为child2.a
变为5;child2
自身属性并没有c这个属性,所以此处的this.c
会指向Child.prototype.c
,故执行结果为Child.prototype.c.demo
的值变为child2.a
的值5,而child2.a
最终自增为6(5 + 1 = 6)。<!-- 加载渲染过程 -->
<!-- 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created ->
子beforeMount -> 子mounted -> 父mounted -->
<!-- 子组件更新过程 -->
<!-- 父beforeUpdate -> 子beforeUpdate -> 子updaed -> 父updated -->
<!-- 父组件跟新过程 -->
<!-- 父beforeUpdate -> 父updated -->
<!-- 销毁过程 -->
<!-- 父beforeDestroy -> 子beforeDestroy -> 子destroyed ->父destroyed -->
// slice(start,[end])
// slice(start,[end])方法:该方法是对数组进行部分截取,该方法返回一个新数组
// 参数start是截取的开始数组索引,end参数等于你要取的最后一个字符的位置值加上1(可选)。
// 包含了源函数从start到 end 所指定的元素,但是不包括end元素,比如a.slice(0,3);
// 如果出现负数就把负数与长度相加后再划分。
// slice中的负数的绝对值若大于数组长度就会显示所有数组
// 若参数只有一个,并且参数大于length,则为空。
// 如果结束位置小于起始位置,则返回空数组
// 返回的个数是end-start的个数
// 不会改变原数组
var arr = [1,2,3,4,5,6]
/*console.log(arr.slice(3))//[4,5,6] 从下标为0的到3,截取3之后的数console.log(arr.slice(0,3))//[1,2,3] 从下标为0的地方截取到下标为3之前的数console.log(arr.slice(0,-2))//[1,2,3,4]console.log(arr.slice(-4,4))//[3,4]console.log(arr.slice(-7))//[1,2,3,4,5,6]console.log(arr.slice(-3,-3))// []console.log(arr.slice(8))//[]*/
// 个人总结:slice的参数如果是正数就从左往右数,如果是负数的话就从右往左边数,
// 截取的数组与数的方向一致,如果是2个参数则截取的是数的交集,没有交集则返回空数组
// ps:slice也可以切割字符串,用法和数组一样,但要注意空格也算字符
// splice(start,deletecount,item)
// start:起始位置
// deletecount:删除位数
// item:替换的item
// 返回值为被删除的字符串
// 如果有额外的参数,那么item会插入到被移除元素的位置上。
// splice:移除,splice方法从array中移除一个或多个数组,并用新的item替换它们。
//举一个简单的例子
var a=['a','b','c'];
var b=a.splice(1,1,'e','f');
console.log(a) //['a', 'e', 'f', 'c']
console.log(b) //['b']
var a = [1, 2, 3, 4, 5, 6];
//console.log("被删除的为:",a.splice(1, 1, 8, 9)); //被删除的为:2
// console.log("a数组元素:",a); //1,8,9,3,4,5,6
// console.log("被删除的为:", a.splice(0, 2)); //被删除的为:1,2
// console.log("a数组元素:", a) //3,4,5,6
console.log("被删除的为:", a.splice(1, 0, 2, 2)) //插入 第二个数为0,表示删除0个
console.log("a数组元素:", a) //1,2,2,2,3,4,5,6
// split(字符串)
// string.split(separator,limit):split方法把这个string分割成片段来创建一个字符串数组。
// 可选参数limit可以限制被分割的片段数量。
// separator参数可以是一个字符串或一个正则表达式。
// 如果separator是一个空字符,会返回一个单字符的数组,不会改变原数组。
var a="0123456";
var b=a.split("",3);
console.log(b);//b=["0","1","2"]
// 注意:String.split() 执行的操作与 Array.join 执行的操作是相反的。
function Dog() {
this.name = 'puppy'
}
Dog.prototype.bark = () => {
console.log('woof!woof!')
}
const dog = new Dog()
console.log(Dog.prototype.constructor === Dog && dog.constructor === Dog && dog instanceof Dog)
输出结果:true
解析: 因为constructor是prototype上的属性,所以dog.constructor实际上就是指向Dog.prototype.constructor;constructor属性指向构造函数。instanceof而实际检测的是类型是否在实例的原型链上。
constructor是prototype上的属性,这一点很容易被忽略掉。constructor和instanceof 的作用是不同的,感性地来说,constructor的限制比较严格,它只能严格对比对象的构造函数是不是指定的值;而instanceof比较松散,只要检测的类型在原型链上,就会返回true。
Object.create() 会创建一个 “新” 对象,然后将此对象内部的 [[Prototype]] 关联到你指定的对象(Foo.prototype)。Object.create(null) 创建一个空 [[Prototype]] 链接的对象,这个对象无法进行委托。
function Foo(name) {
this.name = name;
}
Foo.prototype.myName = function () {
return this.name;
}
// 继承属性,通过借用构造函数调用
function Bar(name, label) {
Foo.call(this, name);
this.label = label;
}
// 继承方法,创建备份
Bar.prototype = Object.create(Foo.prototype);
// 必须设置回正确的构造函数,要不然在会发生判断类型出错
Bar.prototype.constructor = Bar;
// 必须在上一步之后
Bar.prototype.myLabel = function () {
return this.label;
}
var a = new Bar("a", "obj a");
a.myName(); // "a"
a.myLabel(); // "obj a"
//利用绝对定位,先将元素的左上角通过 top:50%和 left:50%定位到页面的中心,然后再通过 translate 来调整元素的中心点到页面的中心。该方法需要考虑浏览器兼容问题。
.parent {
position: relative;
}
.child {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
}
//利用绝对定位,设置四个方向的值都为 0,并将 margin 设置为 auto,由于宽高固定,因此对应方向实现平分,可以实现水平和垂直方向上的居中。该方法适用于盒子有宽高的情况:
.parent {
position: relative;
}
.child {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
//利用绝对定位,先将元素的左上角通过 top:50%和 left:50%定位到页面的中心,然后再通过 margin 负值来调整元素的中心点到页面的中心。该方法适用于盒子宽高已知的情况
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px; /* 自身 height 的一半 */
margin-left: -50px; /* 自身 width 的一半 */
}
//使用 flex 布局,通过 align-items:center 和 justify-content:center 设置容器的垂直和水平方向上为居中对齐,然后它的子元素也可以实现垂直和水平的居中。该方法要**考虑兼容的问题**,该方法在移动端用的较多:
.parent {
display: flex;
justify-content:center;
align-items:center;
}
//另外,如果父元素设置了flex布局,只需要给子元素加上`margin:auto;`就可以实现垂直居中布局
.parent{
display:flex;
}
.child{
margin: auto;
}