1.this指向问题
1.在普通函数中,this指向window
2.构造函数中this指向它的实例
3.普通函数this谁调用就指向谁
4.箭头函数中this指向父级上下文
5.Function.prototype.call 或 Function.prototype.apply 调用跟普通的函数调用相比,用 Function.prototype.call 或 Function.prototype.apply 可以动态地
改变传入函数的 this:
2.
面向对象简称 OO(Object Oriented),20 世纪 80 年代以后,有了面向对象分析(OOA)、 面向对象设计(OOD)、面向对象程序设计(OOP)等新的系统开发方式模型的研究。
对 Java 语言来说,一切皆是对象。把现实世界中的对象抽象地体现在编程世界中,一个对象代表了某个具体的操作。一个个对象最终组成了完整的程序设计,这些对象可以是独立存在的,也可以是从别的对象继承过来的。对象之间通过相互作用传递信息,实现程序开发。
对象的概念
Java 是面向对象的编程语言,对象就是面向对象程序设计的核心。所谓对象就是真实世界中的实体,对象与实体是一一对应的,也就是说现实世界中每一个实体都是一个对象,它是一种具体的概念。对象有以下特点:
- 对象具有属性和行为。
- 对象具有变化的状态。
- 对象具有唯一性。
- 对象都是某个类别的实例。
- 一切皆为对象,真实世界中的所有事物都可以视为对象。
例如,在真实世界的学校里,会有学生和老师等实体,学生有学号、姓名、所在班级等属性(数据),学生还有学习、提问、吃饭和走路等操作。学生只是抽象的描述,这个抽象的描述称为“类”。在学校里活动的是学生个体,即张同学、李同学等,这些具体的个体称为“对象”,“对象”也称为“实例”。
2.面向对象的三大核心特性
##### 面向对象(OOP)。
js是一门面向对象的语言,面向对象是一种给编程思想(万物皆对象)与之对应的是面向过程(类)
js本身就是基于面向对象构建出来的(例如:JS中有很多内置类,Array ,Object ,Function,String像promise就是ES6中新增的一个内置类,我们可以基于new Promise来创建一个实例,管异步编程)
一般我们平时用的Vue/REACT/JQUER也都是基于面向对象构建出来的,他们都是类,平时开发的时候都是创建他们的实例来操作
我在自己真实项目中,也封过一些组件插件指令(面向对象,插件 :NODE路由,sw),他们都是基于面向对象开发的,这样可以创造不同的实例,来管理私有属性和公有的方法。
***面向对象(OOP)类***
1.类 封装 继承 多态
封装:低耦合,高内聚
多态:重载和重写
重载:js中是没有重载概念的(后端:方法名相同,但是形参个数不同或者类型不一样,或者类型不一样,或者返回值不同(类型),根据传参的不同,实现不同的效果)
重写:在类的继承中,子类可以重写父类的方法
3.事件模型:事件委托、代理?如何让事件先冒泡后捕获?
事件冒泡/捕获
事件冒泡 | 事件捕获 |
---|---|
IE和标准浏览器 | 标准浏览器 |
传播顺序:先捕获,后冒泡 | |
早期IE只支持事件冒泡,不支持事件捕获 |
绑定事件
.addEventListener('click',function(){},false) | .attachEvent('onclick',function(){}) |
---|---|
标准浏览器 | IE |
三个参数: 1.事件类型,没有on 2.事件处理函数 3.布尔型的数值,默认false(事件冒泡),true(事件捕获) | 有两个参数: 1.事件类型,有on 2.事件处理函数 |
阻止事件冒泡/捕获 : e.stopPropagation(); | 阻止事件冒泡 : window.event,cancelBubble=true |
问题: 1.顺序相反 2.this指向window |
事件冒泡优点
1.有很多子元素,并且绑定同一种事件,可以委托给父元素代理(事件代理/事件委托)
<body>
<ul id="ul1">
<li>001</li>
<li>002</li>
<li>003</li>
<li>004</li>
<li>005</li>
</ul>
<script>
var oUl = document.getElementById('ul1');
oUl.onclick = function(e){
//获取事件源
var target=e?target = e.target:window.event.srcElement;
console.log(target.innerHTML);
};
</script>
</body>
2.对于后生成事件的绑定,可以用事件委托
<body>
<button id="btn">增加</button>
<ul id="ul1">
<li>001</li>
<li>002</li>
<li>003</li>
<li>004</li>
<li>005</li>
</ul>
<script>
var oUl = document.getElementById('ul1');
var aLi = oUl.getElementsByTagName('li');
var oBtn = document.getElementById('btn');
oUl.onclick = function (e) {
console.log(e.currentTarget);//this
console.log(e.target);//事件源
};
oBtn.onclick = function () {
var oLi = document.createElement('li');
oLi.innerHTML = Math.random();
oUl.appendChild(oLi);
};
</script>
</body>
e.currentTarget=this
e.target获取事件源
可以通过e.currentTarget和e.target的比较得知是不是由事件冒泡触发的
总结:事件模型/事件冒泡/事件委托:利用事件冒泡,把子元素委托给父元素绑定
4.window的onload事件和domcontentloaded
window.onload:
当一个资源及其依赖资源已完成加载时,将触发onload事件。
document.onDOMContentLoaded:
当初始的HTML文档被完全加载和解析完成之后,DOMContentLoaded事件被触发,而无需等待样式表、图像和子框架的完成加载。
区别:
①onload事件是DOM事件,onDOMContentLoaded是HTML5事件。
②onload事件会被样式表、图像和子框架阻塞,而onDOMContentLoaded不会。
③当加载的脚本内容并不包含立即执行DOM操作时,使用onDOMContentLoaded事件是个更好的选择,会比onload事件执行时间更早。
5.for···in和for···of的区别:(for···in取key,for··of取value)
①从遍历数组角度来说,for···in遍历出来的是key(即下标),for···of遍历出来的是value(即数组的值);
var arr = [99,88,66,77];
for(let i in arr){
console.log(i); //0,1,2,3
}
for(let i of arr){
consoel.log(i); //99,88,66,77
}
②从遍历字符串的角度来说,同数组一样。
③从遍历对象的角度来说,for···in会遍历出来的为对象的key,但for···of会直接报错。
var obj = {name:"Bob",age:25};
for(var i in obj){
console.log(i) // name age
}
for(var i of obj){
console.log(i) //报错
}
6.iframe的优缺点有哪些?
优点:
①iframe能够原封不动的把嵌入的网页展现出来;
②如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
③网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。
④如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。
缺点:
①会产生很多页面不易管理;
②iframe框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。
③代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理iframe中的内容,所以使用iframe会不利于搜索引擎优化。
④很多的移动设备(PDA 手机)无法完全显示框架,设备兼容性差。
⑤iframe框架页面会增加服务器的http请求,对于大型网站是不可取的。
本题延申:
frame框架:
优点:
①重载页面时不需要重载整个页面,只需要重载页面中的一个框架页(减少了数据的传输,加快了网页下载速度);
②技术易于掌握,使用方便,使用者众多,可主要应用于不需搜索引擎来搜索的页面;
③方便制作导航栏 ;
缺点:
①搜索引擎程序不能解读这种页面;
②不能打印全框架;
③浏览器的后退按钮无效;
④手机等终端设备无法显示全部框架内容;
iframe和frame区别:
①frame不能脱离frameSet单独使用,iframe可以;
②frame不能放在body中,否则不能正常显示,frame不能和body同时使用,iframe可以;
③嵌套在frameSet中的iframe必需放在body中,不嵌套在frameSet中的iframe可以随意使用;
④frame的高度只能通过frameSet控制;iframe可以自己控制,不能通过frameSet控制;
⑤iframe 可以放到表格里面。frame 则不行。
7.js数组去重
ES6-set
使用ES6中的set是最简单的去重方法
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,
undefined, null,null, NaN,NaN,'NaN', 0, 0, 'a', 'a',{},{}];
function arr_unique1(arr){
return [...new Set(arr)];
//或者
//return Array.from(new Set(arr));
}
arr_unique1(arr); // (13)[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]
利用Map数据结构去重
创建一个空Map数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果。
function arr_unique2(arr) {
let map = new Map();
let array = new Array(); // 数组用于返回结果
for (let i = 0; i < arr.length; i++) {
if(map .has(arr[i])) { // 如果有该key值
map .set(arr[i], true);
} else {
map .set(arr[i], false); // 如果没有该key值
array .push(arr[i]);
}
}
return array ;
}
console.log(arr_unique2(arr)); //(13) [1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN"
利用递归去重
function arr_unique3(arr) {
var array= arr;
var len = array.length;
array.sort(function(a,b){ //排序后更加方便去重
return a - b;
})
function loop(index){
if(index >= 1){
if(array[index] === array[index-1]){
array.splice(index,1);
}
loop(index - 1); //递归loop,然后数组去重
}
}
loop(len-1);
return array;
}
console.log(arr_unique3(arr)); //(14) [1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]
.......
8.什么是数组扁平化?
数组扁平化就是将一个多维数组转换为一个一维数组
实现基本方式
1、对数组的每一项进行遍历。
2、判断该项是否是数组。
3、如果该项不是数组则将其直接放进新数组。
4、是数组则回到1,继续迭代。
5、当数组遍历完成,返回这个新数组
function flatten(arr){
var box = [];
arr.map(v => {
if(Array.isArray(v)){
box = box.concat(flatten(v))
}else{
box.push(v);
}
})
return box;
}
console.log(flatten(arr));
第二种(不推荐):
function flatten(arr){
return arr.toString().split(",").map(v => {
return Number(v);
})
}
console.log(flatten(arr));
第三种:
function flatten(arr){
console.log(arr.join(","))
return arr.join(",").split(",").map(v => {
return parseInt(v);
})
}
console.log(flatten(arr));
第四种:
var arr = [1,2,[3,4,[5,6,[7,8,[9,10]]]]];
function flatten(arr){
return arr.reduce((result,item) => {
console.log(result,item)
return result.concat(Array.isArray(item) ? flatten(item) : item);
},[]);
}
console.log(flatten(arr));
第五种:
console.log([].concat(...arr));
function flatten(arr){
while(arr.some(item => Array.isArray(item))){
arr = [].concat(...arr);
}
return arr;
}
console.log(flatten(arr));
9.垃圾回收机制
垃圾回收机制
浏览器的 Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。其原理是:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。但是这个过程不是实时的,因为其开销比较大并且GC时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行
10。什么是函数柯里化?
百度百科:在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。通俗点说就是将一个函数拆分成多个函数,是固定部分参数,返回一个接受剩余参数的函数,也称为部分计算函数,目的是为了缩小适用范围,创建一个针对性更强的函数。来个简单的例子
// 实现一个简单的加法
function add(a,b){return a+b}
add(1,2);
// 用柯里化实现
const currying = (x) => {
return (y) => {return x+y}
}
console.log(currying(1)(2)) // 3
这里就是使用柯里化把add函数拆分成了两个函数,currying首次执行返回一个新的函数,然后再次调用返回结构,返回一个函数的特征就是上一章所说到的高阶函数,柯里化函数就是高阶函数的一种。这里就会有人提问了,为什么要那么费劲实现add函数?有道经典的面试题实现add(1)(2)(3)(4)=10;
// 我们可以这样理解调用add(1)时返回一个函数fn,然后执行fn(2)依次被调用,当执行到最后一次返回结果
function add(num){
var sum=num;
var fn=function(v){
sum+=v;
return fn
};
fn.toString=function(){
return sum
};
return fn
}
console.log(add(1)(2)(3)(4)) // 10
// 执行add(1)时返回了fn函数给2,3,4执行,同时定义了fn的toString方法,
// 每个对象的toString和valueOf方法都可以被改写,每个对象执行完毕,如果被用以操作JavaScript解析器就会自动调用对象的toString或者valueOf方法
// 利用toString隐式调用的特性,当最后执行时隐式调用,并计算最终的值返回
面试题的内容还有 add(1)(1,2,3)(2)=9 这样的形式,我们上面的方法就没办法执行出正确的答案了,下面来优化一下
function add(){
var args = [...arguments];
var fn=function(){
args.push(...arguments);
return fn
};
fn.toString=function(){
return _args.reduce(function (a, b) {
return a + b;
});
};
return fn
}
consolo.log(add(1)(1,2,3)(2)) // 9
以上就是函数柯里化的简单实现。继续来实现一些例子来看看柯里化的好处
// 实现一个判断数据类型的方法
const checktype = function(type, content) {
return Object.prototype.toString.call(content) === `[object ${type}]`;
}
checktype('Number',2); // true
// 这种方法总是要把type参数传过去,如果写错了就会影响到正确的结果了,可以考虑下如何做到把“Number“做到复用
const curry = function(type){
return function(content){
return Object.prototype.toString.call(content) === `[object ${type}]`;
}
}
const isNumber = curry('Number');
isNumber(3) // true
// 这里就实现参数的复用了,这样的实现给之后的调用带来了很大的便利
函数柯里化是可以给我们带来一下便捷,但是也是会有缺点的,在性能上也会受到影响,比如add函数里面需要创建数组去存放每次调用的时候的参数,创建闭包函数这些都会对内存跟速度上会带来花销,存取arguments对象通常要比存取命名参数要慢一点。