js复习题整理
- 1.cookie的优缺点
- 2.Array.prototype.slice.call(arr,2)方法的作用是:
- 3.以下代码执行后,控制台的输出是:
- 4、简单说一下浏览器本地存储是怎样的
- 5.原型 / 构造函数 / 实例
- 6.原型链:
- 7.执行上下文(EC)一个对象
- 8.变量对象
- 9.作用域链
- 10.闭包
- 11.对象的拷贝
- 12.new 运算符的执行过程
- 13.instanceof 原理
- 14.代码的复用
- 15.继承
- 16.类型转换
- 17.类型判断
- 18.模块化(CommonJS ES6 AMD CMD)
- 19.防抖与节流
- 20.函数执行改变 this
- 21.异步解决方案
- 22.AST
- 23.babel编译原理
- 24.函数柯里化
- 25.get与post
- 28.类的创建和继承
- 29.事件
- 30.懒加载和预加载
- 31.mouseover 和 mouseenter 的区别
1.cookie的优缺点
优点:
- 数据
持久性
。 不需要任何服务器资源
。 Cookie 存储在客户端并在发送后由服务器读取。- 可配置到期规则`。 控制 cookie 的生命期,使之不会永远有效。偷盗者很可 能拿到一个过期的 cookie 。
- 简单性。 基于
文本的轻量结构
。 - 通过良好的编程,控制保存在 cookie 中的
session 对象的大小
。 - 通过加密和安全传输技术
( SSL )
,减少 cookie 被破解
的可能性。 只在 cookie 中存放不敏感数据,即使被盗也不会有重大损失。
缺点:
- Cookie 数量和长度的限制 。 数量:每个域的 cookie 总数有限。每个 cookie 长度
不超过 4KB
( 4096B ),否则会被截掉。 - 潜在的安全风险 。 Cookie
可能被拦截、篡改
。如果 cookie 被拦截,就有 可能取得所有的 session 信息。 用户配置为禁用
。有些用户禁用了浏览器或客户端设备接受 cookie 的能 力,因此限制了这一功能。有些状态不可能保存在客户端
。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到 任何作用。
2.Array.prototype.slice.call(arr,2)方法的作用是:
this 指向 arr,并传递参数 2
,实际上等于 arr.slice(2),即从下标为 2 开 始截取到末尾。
3.以下代码执行后,控制台的输出是:
var a = 10;
function a(){}
console.log(typeof a)
函数提升优先级高于变量提升,以下代码等价为
function a() {};
var a;
a = 10;
console.log(typeof a);
4、简单说一下浏览器本地存储是怎样的
Cookie
存储,明文,大小限制 4k 等localStorage
,持久化存储方式之一,不用在两端之间传输,且限制大小为 10MsessionStorage
,会话级存储方式,浏览器关闭立即数据丢失indexDb
,浏览器端的数据库
5.原型 / 构造函数 / 实例
- 原型:每个 JavaScript 对象中都包含一个 proto 的属性指向它爹(该对象的
原型
),可obj.__proto__
进行访问 - 构造函数: 可以通过
new 来 新建一个对象的函数
。 - 实例: 通过
构造函数和 new 创建出来的对象
,便是实例。 实例通过__proto__ 指向原型,通过 constructor 指向构造函数。 - 注意: 其实
实例上并不是真正有 constructor 这个指针
,它其实是从原型链上获取的
instance.hasOwnProperty('constructor') === false
6.原型链:
- 原型链: 由原型对象组成,每个对象都有
__proto__
属性,指向了创建该对象的构造函数的原型
,__proto__ 将对象连接起来组成了原型链
。是一个用来实现继承和共享属性
的有限对象。 - 属性查找机制: 当查找对象的属性时,如果
实例对象自身不存在
该属性,则沿着原型链往上一级查找
,找到时则输出,不存在时,则继续沿着原型链往上一级查找,直至最顶级的原型对象 Object.prototype
,如还是没找到,则输出 undefined; - 属性修改机制:
只会修改实例对象本身的属性
,如果不存在,则进行添加该属性, 如果需要修改原型的属性时,则可以用: b.prototype.x = 2
;但是这样会造成所有继承于该对象的实例的属性发生改变。
7.执行上下文(EC)一个对象
- 它包含三个部分:
变量对象(VO)
2.作用域链(词法作用域)
3.this 指向
- 它的类型:
1.全局执行上下文
2.函数执行上下文
3.eval 执行上下文
- 代码执行过程:
1.创建全局上下文 (global EC)
全局执行上下文 (caller) 逐行自上而下
执行。
2.遇到函数时
,函数执行上下文 (callee)被 push 到执行栈顶层
,函数执行上下文被激活
,成为active EC
, 开始执行函数中的代码,caller 被挂起
3.函数执行完后
,函数执行上下文被 pop 移除出执行栈
,控制权交还全局上下文 (caller),继续执行。
8.变量对象
执行上下文中的一部分
,可以抽象为一种数据作用域
- 它存储着该执行上下文中的所有
变量和函数声明
(不包含函数表达式)。 - 活动对象 (AO): 当
变量对象所处的上下文为 active EC 时
,称为活动对象。
9.作用域链
- 规定变量和函数的可使用范围称作作用域
- 每个函数都有一个作用域链,查找变量或者函数时,需要从
局部作用域到全局作用域依次查找
,这些作用域的集合
称作作用域链,我们可以在·执行上下文中访问到父级甚至全局的变量·,这便是作用域链的功劳。 - 由两部分组成:
[[scope]] 和 AO
- [[scope]]属性:
指向父级变量对象和作用域链
,也就是包含了父级的[[scope]] 和 AO - AO:
自身活动对象
如此 [[scopr]]包含[[scope]],便自上而下形成一条 链式作用域。
10.闭包
- 闭包就是能够
读取其他函数内部变量的函数
。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数
"。 - 闭包可以用在许多地方。它的最大用处有两个
- 可以
读取函数内部的变量
, - 让这些
变量的值始终保持在内存中
。
- 可以
- 理解代码(一)
var name = "the window";
var obj = {
name: "My Object",
getName() {
var that = this;
return function() {
return that.name;
}
}
}
var result = obj.getName();
console.log(result()); // My Object
- 理解代码(二)
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); // Window
11.对象的拷贝
- 浅拷贝: 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响:
Object.assign()
展开运算符(...)
- 深拷贝: 完全拷贝一个新对象,修改时原对象不再受到任何影响
JSON.parse(JSON.stringify(obj))
: 性能最快具有循环引用的对象报错 ,当值为函数、undefined、或 symbol 时,无法拷贝- 递归逐一拷贝
function deepCopy(target) {
let result;
// 如果当前需要深拷贝的是一个对象的话
if (typeof target === "object") {
// 是一个数组
if (Array.isArray(target)) {
result = [];
for (let i in target) {
result.push(deepCopy(target[i])) //递归克隆
}
} else if (target === null) { //null值直接复制
result = null;
} else if (target.constructor === RegExp) { //RegExp直接复制
result = target;
} else {
// 否则是普通对象,直接for in循环,递归赋值对象的所有值
result = {};
for (let i in target) {
result[i] = deepCopy(target[i])
}
}
} else { // 普通值直接拷贝
result = target;
}
return result;
}
let aObj = {
name: "张三",
age: 14,
hobbies: ["吃饭", "睡觉", "打豆豆"],
}
let bObj = deepCopy(aObj);
console.log(bObj);
bObj.hobbies[0] = "学习";
console.log(aObj);
console.log(bObj);
12.new 运算符的执行过程
- 新生成一个对象
- 链接到原型:
obj.__proto__ = Con.prototype
- 绑定 this: apply
- 返回新对象(如果构造函数有自己 retrun 时,则返回该值)
function myNew(fn, ...args) {
let obj = {};
// 使空对象的隐式原型指向原函数的显式原型
obj.__proto__ = fn.prototype;
// this指向obj
let result = fn.apply(obj, args);
// 返回
return result instanceof Object ? result : obj;
}
13.instanceof 原理
能在实例的 原型对象链
中找到该构造函数的 prototype 属性所指向的原型对 象
,就返回 true
instance.[__proto__...] === instance.constructor.prototype
14.代码的复用
一般有以下的方式:
- 函数封装
- 继承
- 复制extend
- 混入 mixin
- 借用 apply/call
15.继承
原型链继承,也就是通过指定原型
,并可以通过原型链继承原型上的属性或者方法
。
- 圣杯模式
function inherit(tag, ori) {
// 用一个new F()作为中间层来的函数来继承,这样既可以继承ori,又可以独立的给tar.prototype添加一些属性
function F() {};
F.prototype = ori.prototype;
tag.prototype = new F();
tag.prototype.constructor = tag; //构造函数归位
}
function Father() {};
function Son() {};
Father.prototype.name = "张三";
inherit(Son, Father)
let father = new Father();
let son = new Son();
console.log(son.name); //张三
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.cal
l()判断
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.模块化(CommonJS ES6 AMD CMD)
作用: 可维护、 可拓展和可协作性
- 浏览器: 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 执行一次即可。
// 每隔一段时间后执行一次,也就是降低频率,将高频操作优化 成低频操作。n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
function throttle(fn, delay) {
let timer = null;
let startTime = 0; //上一次时间
return function() {
let that = this;
let args = arguments;
// 如果达到了规定的触发时间间隔,触发 handler
if (Date.now() - startTime > delay) {
clearTimeout(timer);
startTime = Date.now();
fn.apply(that, args)
} else { // 没达到触发间隔,重新设定定时器
timer = setTimeout(() => {
fn.apply(that, args)
}, delay)
}
}
}
// 实际想绑定在 scroll 事件上的 handler
// function realFunc() { console.log("Success"); }
// 采用了节流函数
// window.addEventListener('scroll', throttle(realFunc, 500, 1000));
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:当鼠标移出某元素时触发。
不会冒泡
区别:在于子元素连带触发。