手写一个 jsonp

function jsonp(url, params, callback) {
// 判断是否含有参数
let queryString = url.indexOf("?") === -1 ? "?" : "&";

// 添加参数
for (var k in params) {
if (params.hasOwnProperty(k)) {
queryString += k + "=" + params[k] + "&";
}
}

// 处理回调函数名
let random = Math.random()
.toString()
.replace(".", ""),
callbackName = "myJsonp" + random;

// 添加回调函数
queryString += "callback=" + callbackName;

// 构建请求
let scriptNode = document.createElement("script");
scriptNode.src = url + queryString;

window[callbackName] = function() {
// 调用回调函数
callback(...arguments);

// 删除这个引入的脚本
document.getElementsByTagName("head")[0].removeChild(scriptNode);
};

// 发起请求
document.getElementsByTagName("head")[0].appendChild(scriptNode);
}

详细资料可以参考:
《原生 jsonp 具体实现》
《jsonp 的原理与实现》

手写一个观察者模式?

var events = (function() {
var topics = {};

return {
// 注册监听函数
subscribe: function(topic, handler) {
if (!topics.hasOwnProperty(topic)) {
topics[topic] = [];
}
topics[topic].push(handler);
},

// 发布事件,触发观察者回调事件
publish: function(topic, info) {
if (topics.hasOwnProperty(topic)) {
topics[topic].forEach(function(handler) {
handler(info);
});
}
},

// 移除主题的一个观察者的回调事件
remove: function(topic, handler) {
if (!topics.hasOwnProperty(topic)) return;

var handlerIndex = -1;
topics[topic].forEach(function(item, index) {
if (item === handler) {
handlerIndex = index;
}
});

if (handlerIndex >= 0) {
topics[topic].splice(handlerIndex, 1);
}
},

// 移除主题的所有观察者的回调事件
removeAll: function(topic) {
if (topics.hasOwnProperty(topic)) {
topics[topic] = [];
}
}
};
})();

详细资料可以参考:
《JS 事件模型》

EventEmitter 实现

class EventEmitter {
constructor() {
this.events = {};
}

on(event, callback) {
let callbacks = this.events[event] || [];
callbacks.push(callback);
this.events[event] = callbacks;

return this;
}

off(event, callback) {
let callbacks = this.events[event];
this.events[event] = callbacks && callbacks.filter(fn => fn !== callback);

return this;
}

emit(event, ...args) {
let callbacks = this.events[event];
callbacks.forEach(fn => {
fn(...args);
});

return this;
}

once(event, callback) {
let wrapFun = (...args) => {
callback(...args);

this.off(event, wrapFun);
};
this.on(event, wrapFun);

return this;
}
}

一道常被人轻视的前端 JS 面试题

function Foo() {
getName = function() {
alert(1);
};
return this;
}
Foo.getName = function() {
alert(2);
};
Foo.prototype.getName = function() {
alert(3);
};
var getName = function() {
alert(4);
};
function getName() {
alert(5);
}

//请写出以下输出结果:
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3

详细资料可以参考:
《前端程序员经常忽视的一个 JavaScript 面试题》
《一道考察运算符优先级的 JavaScript 面试题》
《一道常被人轻视的前端 JS 面试题》

如何确定页面的可用性时间,什么是 Performance API?

Performance API 用于精确度量、控制、增强浏览器的性能表现。这个 API 为测量网站性能,提供以前没有办法做到的精度。

使用 getTime 来计算脚本耗时的缺点,首先,getTime方法(以及 Date 对象的其他方法)都只能精确到毫秒级别(一秒的千分之一),想要得到更小的时间差别就无能为力了。其次,这种写法只能获取代码运行过程中的时间进度,无法知道一些后台事件的时间进度,比如浏览器用了多少时间从服务器加载网页。

为了解决这两个不足之处,ECMAScript 5引入“高精度时间戳”这个 API,部署在 performance 对象上。它的精度可以达到1毫秒
的千分之一(1秒的百万分之一)。

navigationStart:当前浏览器窗口的前一个网页关闭,发生 unload 事件时的 Unix 毫秒时间戳。如果没有前一个网页,则等于 fetchStart 属性。

loadEventEnd:返回当前网页 load 事件的回调函数运行结束时的 Unix 毫秒时间戳。如果该事件还没有发生,返回 0。

根据上面这些属性,可以计算出网页加载各个阶段的耗时。比如,网页加载整个过程的耗时的计算方法如下:

var t = performance.timing;
var pageLoadTime = t.loadEventEnd - t.navigationStart;

详细资料可以参考:
《Performance API》

js 中的命名规则

(1)第一个字符必须是字母、下划线(_)或美元符号($)
(2)余下的字符可以是下划线、美元符号或任何字母或数字字符

一般我们推荐使用驼峰法来对变量名进行命名,因为这样可以与 ECMAScript 内置的函数和对象命名格式保持一致。

详细资料可以参考:
《ECMAScript 变量》

js 语句末尾分号是否可以省略?

在 ECMAScript 规范中,语句结尾的分号并不是必需的。但是我们一般最好不要省略分号,因为加上分号一方面有
利于我们代码的可维护性,另一方面也可以避免我们在对代码进行压缩时出现错误。

Object.assign()

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

Math.ceil 和 Math.floor

Math.ceil() === 向上取整,函数返回一个大于或等于给定数字的最小整数。

Math.floor() === 向下取整,函数返回一个小于或等于给定数字的最大整数。

js for 循环注意点

for (var i = 0, j = 0; i < 5, j < 9; i++, j++) {
console.log(i, j);
}

// 当判断语句含有多个语句时,以最后一个判断语句的值为准,因此上面的代码会执行 10 次。
// 当判断语句为空时,循环会一直进行。