手写常见函数
实现一个 new 方法
new 运算符用于创建一个对象类型的实例或具有构造函数的内置对象的实例。
new 关键字会进行如下的操作:
- 创建一个空的简单的 JavaScript 对象(即:
{}); - 设置该对象的构造函数到另一个对象上;
- 将步骤 1 新创建的对象作为 this 的上下文;
- 如果该对象没有返回对象,则返回
this;
实现:
function myNew(Class, ...args){
var obj = {};
// obj 是实例,实例对象的原型应指向构造函数的原型
obj.__proto__ = Class.prototype;
var result = Class.call(obj, ...args);
return result instanceof Object ? result : obj;
}
上面代码中我们该判断了 result 的类型,在原生的 new 关键字上,如果你返回了一个对象,则接收时接收的会是这个对象,例如:
function Per(name, age){
this.name = name;
this.age = age;
this.getName = function(){
return this.name;
}
// 返回了一个对象
return {};
}
let p = new Per("XiaoMing", 18); // {}
如果 result 是一个对象类型的数据,则就返回它,不是的话就返回我们创建的对象实例。
实现 apply、call 方法
apply 与 call 方法类似,不同的是 apply 的第二个参数一个数组。它们都是用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数,该函数内部的 this 指向会变成我们指定的 this。
实现思路:
- 给传入的 this 动态定义一个方法,这个方法就是要调用的函数;
- 执行 this 上的这个方法;
- 删掉 this 上的这个方法;
代码:
Function.prototype.myCall = function(context, ...args){
// context 是假值或者没传时,让它是 window
context = context || window;
// 避免方法名重复,使用 symbol 类型作为方法名
var key = Symbol();
// 这里的 this 指代被调用的函数
context[key] = this;
// 执行这个方法,自然里面的 this 指向 context
const result = context[key](...args);
// 执行完后删掉这个方法
delete context[key];
// 返回最终的结果
return result;
}
apply 方法与之类似:
Function.prototype.myApply = function(context, args){
context = context || window;
var key = Symbol();
context[key] = this;
const result = context[key](...args);
delete context[key];
return result;
}
实现 bind 函数
bind 方法与 call 方法类似,但它不会调用函数,只是绑定 this 和函数参数。如果使用 new 运算符构造绑定函数,则忽略传入的 this 参数。例如下面的代码:
function Per(name){
this.name = name;
this.age = 18;
return this.name + "\t" + this.age;
}
var P = Per.bind({}, "M");
var p = new P(); // bind 的第一个参数将会忽略
实现代码:
Function.prototype.myBind = function(context, ...args){
context = context || window;
// 把原来的函数保存起来
var fn = this;
// 返回一个函数
return function newFn(...newArgs){
// 如果是构造函数,this 将是 newFn 的实例
if(this instanceof newFn){
return new fn(...args, ...newArgs);
}
// 否则就绑定 this 并执行
return fn.call(context, ...args, ...newArgs);
}
}
实现 instanceof
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。例如:
function C(){}
var o = new C();
o instanceof C; // true
o instanceof Object; // true
function D(){}
o instanceof D; // false
实现:
function myInstanceof(object, constructor){
let proto = Object.getPrototypeOf(object);
let prototype = constructor.prototype;
while(true){
// 原型链的最上层是 null
if(proto === null) return false;
if(proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
getPrototypeOf 方法可以返回指定对象的原型,相当于 object.__proto__。如果没有继承,则返回 null。
Object.prototype.proto === null // true
sleep 函数
这个函数很像 setTimeout 函数,可以作为延时函数。
var sleep = function(delay){
return new Promise(resolve => {
setTimeout(resolve,delay);
});
}
// test
function getName(name){
sleep(1000)
.then(() => {
console.log(name);
})
}
// 执行后,大概一秒后才能打印出 name 的值
getName();
或者使用 ES7 中的 async/await:
async function getName(name){
await sleep(1000);
console.log(name);
}
防抖、节流
防抖:
function debounce(fn, delay) {
let timer = null;
return function(...args) {
let self = this;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(self, args);
}, delay);
}
}
节流:
function throttle(fn, delay) {
let timer = null;
return function(...args) {
let self = this;
// 首次调用不延迟
if(!timer) {
fn.apply(self, args);
}
if(timer) return;
timer = setTimeout(function() {
fn.apply(self, args);
timer = null;
}, delay);
}
}
防抖和节流的不同
防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。
HTML 模板解析
题目如下:
<script id="test" type="text/html">
<h1>{{title}} world!</h1>
<div>
<h3>My name is {{data.name}}</h3>
<h4>I'm {{data.age}}</h4>
</div>
</script>
<script>
const user = {
title: 'hello',
data: {
name: 'XiaoMing',
age: 20
}
};
// template 会把数据填充到模板里
const html = template('test', user);
</script>
可以使用正则表达式来实现:
function template(id, data) {
let content = document.getElementById(id).innerHTML;
// 匹配 {{...}}
const templateReg = /\{\{\s*(.+)\s*\}\}/g;
// 匹配 a.b[1] 形式的数组
const arrayReg = /(\w+)\s*\[(\d+)\]/g;
// 主项就行匹配
let templateData = templateReg.exec(content);
while(templateData) {
// 拿到整体的模板 {{...}} 和 其中的属性
let [templateStr, protoStr] = templateData;
// 分离属性链
let protoArr = protoStr.split('.');
let tempData = data;
// 遍历
protoArr.forEach(proto => {
// 看看有没有数组
let execArr = arrayReg.exec(proto);
if(execArr) {
let [, protoName, index] = execArr;
tempData = tempData[protoName][index];
} else {
tempData = tempData[proto];
}
});
content = content.replace(templateStr, tempData);
templateData = templateReg.exec(content);
}
return content;
}
写一个函数,两秒获取数组中的下一个元素
代码如下:
function run(array, callback) {
let len = array.length, idx = 0;
function play() {
if(idx < len) {
callback(array[idx], idx);
idx += 1;
setTimeout(play, 2000);
}
}
setTimeout(play, 2000);
}
const ary = [
'hello',
'world',
'你好呀~',
'My name is Ming'
];
run(ary, function(item) {
console.log(item);
});
ES5 和 ES6 中的继承
ES5 版:
function Super(age, gender) {
this.age = age;
this.gender = gender;
}
Super.prototype.sayAge = function() {
return this.age;
}
function Sub(name, age, gender) {
this.name = name;
Super.call(this, age, gender);
}
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.sayName = function() {
return this.name;
}
let sub = new Sub('ming', 18, 'male');
console.log(sub.sayName());
console.log(sub.sayAge());
ES6 版:
class Super{
constructor(age, gender) {
this.age = age;
this.gender = gender;
}
sayAge() {
return this.age;
}
}
class Sub extends Super{
constructor(name, age, gender) {
this.name = name;
super(age, gender);
}
sayName() {
return this.name;
}
}
Promise 版 Ajax
function ajax(url, options) {
options = Object.assign({
method: 'GET',
headers: {},
data: null
}, options);
return new Promise((resolve, reject) => {
let { method, data, headers } = options;
let xhr = new XMLHttpRequest();
xhr.open(method, url, true);
for(let header in headers) {
xhr.setRequestHeader(header, headers[header]);
}
// readyState 的值发生变化时,触发这个事件
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
if(xhr.status === 200 || xhr.status === 304) {
resolve(xhr.response);
} else {
reject(xhr);
}
}
}
xhr.onerror = function(e) {
reject(e);
}
xhr.send(data);
});
}
深拷贝解决循环引用问题
使用 WeakMap 保存数组或对象,在我们不使用某个对象时(比如手动赋值为 null),垃圾回收机制会自动帮我们回收。代码如下:
const isObject = (obj) => typeof obj === 'object' && obj !== null;
const isArray = (ary) => Array.isArray(ary);
function deepClone(target) {
let map = new WeakMap();
function clone(target) {
if(isObject(target)) { // 引用类型
let result = isArray(target) ? [] : {};
if(map.get(target)) { // map 中有这个对象就直接返回
return map.get(target);
}
// 没有这个对象就先存入,可能之后的遍历还会出现这个对象
map.set(target, result);
for(let key in target) {
// 递归克隆
result[key] = clone(target[key]);
}
return result;
} else {
return target;
}
}
return clone(target);
}