JS复习题 二
16.类型转换
JS 中在使用运算符号或者对比符时,会自带隐式转换
- 数字 + 字符串 = 字符串
let a = 123 + "456"; //"123456"
- 数字 + 对象, 优先调用对象的 valueOf -> toString
- 数字 + boolean/null -> 数字
- 数字 + undefined -> NaN
- [1].toString() === ‘1’
- {}.toString() === ‘[object object]’
- NaN !== NaN
- +undefined 为 NaN
17.类型判断
- 1.基本类型(typeof)
string number undefined null boolean function
缺点: 对于null,[ ],{ }均检测出为object,不能进一步判断它们的类型 - 2.引用类型(instance of)
判断某个实例是不是属于原型
function Fruit(name, price) {
this.name = name;
this.price = price
}
let apple = new Fruit("apple", "5")
console.log(apple instanceof Object); //true
console.log(apple instanceof Array); //false
- 3.Object.prototype.toString.call()判断
call()方法可以改变this的指向,把Object.prototype.toString()方法指向不同的数据类型上面,返回不同的结果
Object.prototype.toString.call(1)
//[object Number]
- 4.完美方式
function _typeOf(obj) {
let s = Object.prototype.toString.call(obj);
console.log(s); //[object Number]
return s.match(/\[object (.*?)\]/)[1].toLowerCase();
}
console.log(_typeOf(1));
18.模块化
作用: 可维护、 可拓展和可协作性
- 浏览器: ES6 的模块化支持
import / export - Node: commonjs 的模块化支持
require / module.exports / exports - amd
require / defined
require 与 import 的区别 :
require 支持 动态导入,import不支持,正在提案 (babel 下可支持) require 是 同步 导入,import 属于异步导入
require 是值拷贝,导出值变化不会影响导入值;import 指向 内存地址,导 入值会随导出值而变化
19.防抖与节流
作用:高频触发优化方式,能对性能有较大的帮助
- 防抖 (debounce): 将多次高频操作优化为只在最后一次执行。n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
使用场景 : 用户输入,只需再输入完成后做一次输入校验即可。
// 第一次触发,最后一次也触发
function doubounce(fn, wait, flag) {
let timer = null;
return function() {
let that = this; //保存this
let args = arguments //保存event对象
clearTimeout(timer); //如果误触则重新计时
if (!timer && flag) {
fn.apply(that, args)
}
timer = setTimeout(() => {
fn.apply(that, args)
}, wait)
}
}
- 节流(throttle): 每隔一段时间后执行一次,也就是降低频率,将高频操作优化 成低频操作。n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
使用场景: 滚动条事件或者 resize 事件,通常每隔 100~500 ms 执行一次即可。
function throtthed(fn, delay) {
let pevTime = 0;
let timer = null;
return function() {
let that = this;
let args = arguments;
if (Date.now() - pevTime > delay) {
clearTimeout(timer);
timer = null;
pevTime = Date.now();
fn.apply(that, args)
} else if (!timer) {
timer = setTimeout(() => {
timer = null;
fn.apply(that, args)
}, delay)
}
}
}
20.函数执行改变 this
this 指向,其实就是函数的运行环境,也就是谁调用了函数。
改变this指向call apply bind
- apply
// apply(用数组的形式传递参数)
let array1 = [12, "foo", { name: "Joe" }, -2458];
let array2 = ["Doe", 555, 100];
Array.prototype.push.apply(array1, array2);
console.log(array1); //[ 12, 'foo', { name: 'Joe' }, -2458, 'Doe', 555, 100 ]
- call
// call(代理log方法,添加前缀)
function log() {
let args = Array.prototype.slice.call(arguments); //arguments数组转换为标准数组
args.unshift("(app)")
console.log.apply(console, args);
}
log(1)
log(1, 2)
区别:apply和call传递参数的方式不一样,apply传递一个数组
func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2])
- bind
bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
var foo = {
bar : 1,
eventBind: function(){
var _this = this;
$('.someClass').on('click',function(event) {
/* Act on the event */
console.log(_this.bar); //1
});
}
}
使用bind()
var foo = {
bar : 1,
eventBind: function(){
$('.someClass').on('click',function(event) {
/* Act on the event */
console.log(this.bar); //1
}.bind(this));
}
}
21.异步解决方案
- 1.Promise
- 2.generator
yield: 暂停代码
next(): 继续执行代码
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}
let f = foo(5);
console.log(f.next()); //{ value: 6, done: false }
console.log(f.next()); //{ value: 7, done: false }
console.log(f.next()); //{ value: 8, done: true }
- 3.async await 对比 Promise
const doSomething = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve("做些事情")
}, 3000)
})
}
const watch = async() => {
const something = await doSomething();
return something + ' 查看';
}
const watchAgain = async() => {
const something = await watch();
return something + " 再次查看"
}
watchAgain().then((res) => {
console.log(res);
})
// 做些事情 查看 再次查看
22.AST
抽象语法树(Abstract Syntax Tree,AST),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构
,树上的每个节点
都表示源代码中的一种结构
。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似于 if-condition-then 这样的条件跳转语句,可以使用带有三个分支的节点来表示。
23.babel编译原理
- babylon将 ES6/ES7 代码
解析成 AST
- babel-traverse 对 AST 进行遍历转译,得到
新的 AST
- 新 AST 通过 babel-generator
转换成 ES5
24.函数柯里化
柯里化(Currying),维基百科上的解释是,把接受多个参数的函数转换成接受一个单一参数的函数。
function add(a, b) {
return a + b;
}
function curryingAdd(a) {
return function(b) {
return a + b;
}
}
console.log(add(1, 2));
console.log(curryingAdd(1)(2));
- 参数复用
function curryingCheck(reg) {
return function(txt) {
return reg.test(txt);
}
}
let hasNumber = curryingCheck(/\d+/g);
let hasLetter = curryingCheck(/[a-z]+/g);
console.log(hasNumber("test1")) //true
console.log(hasNumber("testtest")) //false
console.log(hasLetter("212121")); //true
- 封装
function processCurrying(fn, args) {
let that = this;
let argArr = args || [];
return function() {
let _args = Array.prototype.slice.apply(arguments); //定义一个数组存储所有的参数
Array.prototype.push.apply(argArr, _args);
// 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
if (_args.length < fn.length) {
return processCurrying.call(that, fn, _args)
}
// 参数收集完毕,执行fn
return fn.apply(this, _args)
}
}
25.get与post
- 1.get 请求传参长度的误区
误区:我们经常说 get 请求参数的大小存在限制,而 post 请求的参数大小是无 限制的。
HTTP 协议 未规定 GET 和 POST 的长度限制
浏览器和 web 服务器限制了 URI 的长度
不同的浏览器和 WEB 服务器,限制的最大长度不一样
IE : 最大长度为 2083byte,
Chrome : 最大长度 8182byte - 2.get 和 post 请求在缓存方面的区别
get可以使用缓存:请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接。
post不能使用缓存:post 做的一般是修改和删除的工作,所以必须与数据库交互。
28.类的创建和继承
…
29.事件
1.事件流
概念:事件流描述的是从页面中接收事件的顺序
DOM2级事件规定的事件流包括三个阶段:
- 事件捕获阶段
- 处于目标阶段
- 事件冒泡阶段
2.冒泡机制
1.从被点击元素开始
2.检查元素是否有事件的处理方法: a. 如果有对应的事件方法,就调用事件方法,然后进入步骤3 b. 如果没有事件方法,进入步骤3
3.找到元素的父元素,然后对父元素进行步骤2的操作。直到html根节点。
grandfather.onclick = function() {
console.log("根组件");
};
father.onclick = function() {
console.log("父组件")
};
son.onclick = function() {
console.log("孙组件")
};
3.捕获机制
addEventListener第三个参数为true,则使用捕获机制
1.从html元素开始
2.检查元素是否有事件的处理方法: a. 如果有对应的事件方法,就调用事件方法,然后进入步骤3(叔叔节点也会响应事件) b. 如果没有事件方法,进入步骤3
3.找到元素的子元素,然后对子元素进行步骤2的操作。直到到达实际点击的元素。
function grandfatherClick() {
console.log("根组件");
};
function fatherClick() {
console.log("父组件");
};
function sonClick() {
console.log("子组件");
}
grandfather.addEventListener("click", grandfatherClick, true)
father.addEventListener("click", fatherClick, true)
son.addEventListener("click", sonClick, true)
这是因为事件的捕获机制导致的。 阻止冒泡行为, 调用event对象的stopPropagation()方法
4.先冒泡后捕获
对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被执行完后再执行捕获事件。
5.事件委托
事件委托指的是,不在事件的发生地(直接 dom)上设置监听函数,而是 在其父元素上设置监听函数
,通过事件冒泡
,父元素可以监听到子元素上事件的触发
,通过判断事件发生元素 DOM 的类型,来做出不同的响应。
应用:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用 事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。
好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制
30.懒加载和预加载
- 预加载:
提前加载图片
,当用户需要查看时可直接从本地缓存中渲染
。(会增加服务器前端压力) - 懒加载:懒加载的主要目的是作为服务器前端的优化,
减少请求数
或延迟请求数
。(缓解压力作用)
31.mouseover 和 mouseenter 的区别
- mouseover:当鼠标移入某元素时触发,移入和移出其子元素时也会触发。
- mouseout:当鼠标移出某元素时触发,移入和移出其子元素时也会触发。
所以有一个重复触发,冒泡的过程
- mouseenter:当鼠标移入某元素时触发。
- mouseleave:当鼠标移出某元素时触发。
不会冒泡
区别:在于子元素连带触发。
32.js各种位置
- clientHeight:可视区域的高度,不包含 border 和滚动条
- offsetHeight:可视区域的高度,包含了 border 和滚动条
- scrollHeight:所有区域的高度,包含了因为滚动被隐藏的部分。
- clientTop:边框 border 的厚度,在未指定的情况下一般为 0
- scrollTop:滚动后被隐藏的高度,获取对象相对于由 offsetParent 属性指定的 父坐标(css 定位的元素或 body 元素)距离顶端的高度。
33.原生js拖拽的实现
思路:
- 1.设置一个拖拽标识
- 2.onmousedown 鼠标按下事件
拖拽标识为true
获取鼠标XY
获取元素的XY - 3.onmousemove 鼠标移动事件
元素现在X = 鼠标现在X-鼠标原来X+元素原来X;
元素现在Y = 鼠标现在Y-鼠标原来Y+元素原来Y; - 4.onmouseup 鼠标离开事件
拖拽标识为false
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
position: relative;
width: 100px;
height: 100px;
background-color: pink;
cursor: move;
}
</style>
</head>
<body>
<div class="box" id="drag"></div>
</body>
<script>
let dragObj = document.getElementById("drag");
let mouseX, mouseY, objX, objY;
let dragging = false; // 设置拖拽标识
// 鼠标按下事件
dragObj.onmousedown = function(event) {
dragging = true;
mouseX = event.clientX;
mouseY = event.clientY;
objX = dragObj.style.left;
objY = dragObj.style.top;
};
// 鼠标移动事件
dragObj.onmousemove = function(event) {
if (dragging) {
dragObj.style.left = parseInt(event.clientX - mouseX + objX) + "px";
dragObj.style.top = parseInt(event.clientY - mouseY + objY) + "px";
}
};
// 鼠标离开事件
dragObj.onmouseup = function(event) {
dragging = false;
};
</script>
</html>
34.异步加载js的办法
- 1.将script标签放到body底部
不会造成页面解析的阻塞,就算加载时间过长用户也可以看到页面而不是一片空白,而且这时候可以在脚本中操作DOM。 - 2.defer属性(将脚本文件设置为延迟加载)
当浏览器遇到带有defer属性的script标签,会再开启一个线程去下载js文件,同时继续解析HTML文档,等等HTML全部解析完毕DOM加载完成之后,再去执行加载好的js文件。
这种方式只适用于引用外部js文件的script标签,多个则按顺序执行,添加defer属性的js文件不应该使用document.write方法。 - 3.async属性
也是会开启一个线程去下载js文件,但和defer不同的时,它会在下载完成后立刻执行,而不是会等到DOM加载完成之后再执行,所以还是有可能会造成阻塞。
同样的,async也是只适用于外部js文件,也不能在js中使用document.write方法,但是对多个带有async的js文件,它不能像defer那样保证按顺序执行,它是哪个js文件先下载完就先执行哪个。 - 4.动态创建script标签
(function() {
if (window.attachEvent) {
window.attachEvent("load", asyncLoad);
} else {
window.addEventListener("load", asyncLoad);
}
let asyncLoad = function() {
let script = document.createElement("script");
script.type = "text/script";
script.async = true;
script.src = "";
let s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(script, s);
}
})()