闭包 和 作用域
作用域
作用域是指在程序中定义变量的区域,作用域规定了变量与函数的活动范围。
在 es6 之前,js 中只有全局作用域和函数作用域。在这之后,引入了 let 和 const ,它们会创建块级作用域。
在函数调用栈中,当内部函数查找一个外部变量,则 js 引擎就会向上级作用域中查找。这个查找的链条称为作用域链。
闭包
闭包的目的是能在函数的外部作用域中访问函数内部的上下文。通过作用域以及作用域链,我们知道了当前作用域中(outer)的上下文(变量),会在当前调用栈结束调用后被 gc 销毁。但是通过作用域链,我们可以在一个内部函数(inner)中访问 outer 作用域中的变量,创建引用关系。这样,就算调用栈执行完毕后也不会销毁 outer 中被引用的执行上下文。
function outer() {
// outer
var a = 1;
return {
getA: function () {
// inner
return a;
},
setA: function (v) {
// inner
a = v;
},
};
}
你是如何组织自己的代码?是使用模块模式,还是使用经典继承的方法?
正常些 js 方法时使用模块模式,一个 IIFE 就算是一个模块,互相之间不影响。
继承在 react 跟 node 中也是比较常用的。
var jspy = (function () {
var _count = 0;
var incrementCount = function () {
_count++;
};
var getCount = function () {
return _count;
};
return {
incrementCount: incrementCount,
getCount: getCount,
};
})();
匿名函数的典型用法
匿名函数就是没有名称的函数, 如:
(function () {})();
!(function () {})();
void (function () {})();
function foo() {
return function () {};
}
宿主对象(host object) 和原生对象(native object)、内置对象的区别
请指出以下代码的区别:function Person(){}、var person = Person()、var person = new Person()?
- 定义了一个名为 Person 的函数
- 普通的调用 Person 函数,并将返回值赋值到 person 变量上。
- 使用 new 关键字,将 Person 作为构造函数实例化
call apply bind
Function.prototype.call = function(context) {
// this传参为null则指向window
var context = context || window;
context.fn = this;
var args = [];
for (var i = 1, len = arguments.length; i < len ; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args +')');
delete context.fn;
return result;
}
Function.prototype.apply = function(context, arr) {
var context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
} else {
var args = [];
for (var i = 0 , len = arr.length; i< len ; i++) {
arr.push('arr['+i+']');
}
result = eval('context.fn(+' args '+)')
}
delete context.fn
return result;
}
Function.prototype.bind = function(context) {
var outerArgs = Array.prototype.slice.call(arguments, 1);
// 实例
var self = this;
return function() {
var innerArgs = Array.prototype.slice.call(arguments);
var args = outerArgs.concat(innerArgs)
return self.apply(context, args)
}
}
new 实现
function fakeNew() {
var obj = Object();
var Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);
return typeof ret === "object" ? ret : obj;
}
函数防抖和函数节流
防抖函数概念:
该函数只能在指定延时结束后才能调用,如果在过程中重复调用,则重新计时。
应用场景:
- 用户在搜索框输入时的数据查询,指定
n
毫秒 延时,只能在输入完后过了n
毫秒后才会去搜索,中间的持续输入则会重新计时 - 在监听 window.onresize 事件,并触发某些操作时。
function debounce(fn, interval) {
var timer;
return function () {
var _self = this;
if (timer) clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(_self, arguments);
}, interval);
};
}
函数节流:
在规定时间内,函数只能被调用一次。如果单位时间内多次触发,则忽略
应用场景:
- 点击搜索按钮时的防重复。
- 监听 scroll 事件时
function throttle(fn, interval) {
var last = 0;
return function () {
var now = +new Date();
if (now - last >= interval) {
fn.apply(this, arguments);
last = +new Date();
}
};
}
什么情况下会使用 document.write()
- 加载需要配合 js 脚本使用的外部 css 文件
<scirpt>
document.write('<link rel="stylesheet" href="style_neads_js.css">');
</script>
- 在新打开的页面中写入数据时
由于 document.write 会重写整个页面,异步调用会影响本页面的文档,如果在新窗口空白页调用,就没影响了。新开一个窗口,把本页面取到的数据在新窗口展示。
document.open();
document.write("hello world");
document.close();
特性检测、特性推断、浏览器 UA 字符串嗅探
Ajax 工作原理
跨域方案
图片 Ping
图片 Ping 是客户端向服务器的单向通信,因为 src 请求资源不属于同源策略,所以一般可以用来做埋点、前端监控,比如监听网页的 PV(Page View),UV(Unique Visitor)。
最好是采用 1 * 1 像素的透明 gif 图,因为:
- 能够完成整个 HTTP 请求+响应(尽管不需要响应内容)
- 触发 GET 请求之后不需要获取和处理数据、服务器也不需要发送数据
- 跨域友好
- 执行过程无阻塞
- 相比 XMLHttpRequest 对象发送 GET 请求,性能上更好
- GIF 的最低合法体积最小(最小的 BMP 文件需要 74 个字节,PNG 需要 67 个字节,而合法的 GIF,只需要 43 个字节)
变量声明提升
js 冒泡机制
property(属性)和 attribute(特性)
DOM 有其默认的基本属性,而这些属性就是所谓的“property”,无论如何,它们都会在初始化的时候再 DOM 对象上创建。
HTML 标签中定义的属性和值会保存该 DOM 对象的 attributes 属性里面;
这些 attribute 属性的 JavaScript 中的类型是 Attr,而不仅仅是保存属性名和值这么简单;
<input id="in_2" sth="whatever" />
var in2 = document.getElementById("in_2");
console.log(in2);
// id: "in_2"
// value: null
console.log(in1.attibutes.sth); // 'sth="whatever"'
document load 和 document DOMContentLoaded
- load 方法在网页中所有的资源(HTML,CSS,image)都完全加载后才触发
- 当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载
== 和 === 有什么不同
同源策略 (same-origin policy)
strict 模式
为何通常会认为保留网站现有的全局作用域 (global scope) 不去改变它,是较好的选择
- 减少命名冲突
- 有利于模块化
请解释什么是单页应用 (single page app), 以及如何使其对搜索引擎友好 (SEO-friendly)
单页应用是指在浏览器中运行的应用,它们在使用期间不会重新加载页面。像所有的应用一样,它旨在帮助用户完成任务,比如“编写文档”或者“管理 Web 服务器”。可以认为单页应用是一种从 Web 服务器加载的富客户端
使用服务端渲染
Promise 如何使用,实现
可变对象和不可变对象
可变对象
我们知道,JavaScript 中对象是弱类型的。一般情况下,可以不受限制的为对象添加属性,修改属性,删除属性。大部分情况下,我们使用的都是可变对象。
不可变对象
对应的,我们不希望代码中某些对象被任意修改,比如添加、修改、删除等。这就是我们的不可变对象。JavaScript 为我们提供了一些原生方法,借助它们可以讲一些可变对象转变成不可变对象。一共有三种:不可扩展,密封,冻结。
- Object.preventExtensions(obj) 不可扩展(无法阻止深层属性的扩展)
function Person(name) {
this.name = name;
}
var person1 = new Person("william");
Object.preventExtensions(person1); // 阻止扩展属性
person1.age = 10;
console.log(person1); // {name: 'william'}
var person2 = {
name: "william",
info: {
age: 23,
height: "183cm",
},
};
Object.preventExtensions(person2);
person2.info.sex = "male"; // 可以扩展深层属性
person2.info.sex; // male
Object.seal(obj) 方法可以让一个对象密封,并返回被密封后的对象。密封对象将会阻止向对象添加新的属性。另外也会 改变属性的 configurable 描述。
Object.freeze(obj) 方法可以冻结一个对象。冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。也就是说,这个对象永远是不可变的。该方法返回被冻结的对象。
什么是事件循环
let var const
数组的方法
web worker
函数柯里化
创建对象的三种方法
- 构造函数:
var obj = new Object();
- 对象字面量:
var obj = {};
Object.create(obj[, otherobj])
;
深拷贝和浅拷贝
var obj = {
name: "william",
age: 20,
other: {
work: "code",
eat: ["胡萝卜", "西瓜", { test: 1 }],
},
};
var arr = [1, 2, 3, [4, 5, { test: 6 }]];
function deepClone(data) {
var type = Object.prototype.toString.call(data);
var newData;
if (type === "[object Object]") {
newData = {};
for (var key in data) {
if (
Object.prototype.toString.call(data[key]) !== "[object Object]" &&
Object.prototype.toString.call(data[key]) !== "[object Array]" &&
hasOwnProperty.call(data, key) &&
!hasOwnProperty.call(newData, key)
) {
newData[key] = data[key];
} else {
newData[key] = deepClone(data[key]);
}
}
} else if (type === "[object Array]") {
newData = [];
for (var i = 0; i < data.length; i++) {
if (
Object.prototype.toString.call(data[i]) !== "[object Object]" &&
Object.prototype.toString.call(data[i]) !== "[object Array]"
) {
newData.push(data[i]);
} else {
newData.push(deepClone(data[i]));
}
}
} else {
return data;
}
return newData;
}
网页上各种高度
实现页面加载进度条
箭头函数和普通函数有什么区别
箭头函数 | 普通函数 | |
---|---|---|
写法 | var fn = () => {} | function fn() {} |
arguments | 无 | 有 |
this | 运行时向父作用域查找,直到 window | 1. 作为函数调用,指向 window 2. 作为对象的方法调用,指向当前对象 |
new | 不能使用 | 函数作为构造函数 |
原型属性 | 没有原型属性console.log(fn.prototype) //undefined |
有原型属性 |