一些学习记录

Object.create

该方法返回一个新对象,且指定该对象的原型。

1
2
3
4
5
6
7
const obj = {
  name: 'tim',
  age: 18,
}
const newObj = Object.create(obj);
console.log(Object.getPrototypeOf(newObj) === obj); //true
console.log(newObj.__proto__ === obj); //true (__proto__兼容性略差)

利用它可以实现继承:

1
2
Child.prototype = Object.create(Parent.prototype);
Child.protype.constructor = Child;

尝试研究到底做了什么:

const create = (obj) => {
  function Noop() {}
  Noop.prototype = obj;
  return new Noop();
}

查找一个对象的属性到底做了什么?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const lookupProperty = (object, propertyName) => {
  let current = object;

  if (current === null) {
    throw new Error(`Can't read ${propertyName} of ${current}`);
  }

  while(current) {
    if (current.hasOwnProperty(propertyName)) {
      return current[propertyName];
    }
    current = Object.getPrototypeOf(current);
  }

  return undefined;
}

私有变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const person = (function() {
  let _name = 'tim';
  return {
    get name() {
      return _name;
    },
    set name(_val) {
      _name = _val;
    }
  }
})();
person.name // tim
person._name // undefined

ES5实现一个生成迭代器函数

迭代器的特性: 首先迭代器是一个对象,它拥有next方法,每次调用next方法返回一个结果对象,结果对象拥有done和value两个属性。next方法的不停地调用,最终所有元素都已被遍历,自此再调用next方法,done变为true,且value为undefined(不严谨,若生成器里return了一个值,那么value就是那个值)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function createIterator(items) {
  let count = 0;
  return {
    next: function() {
      const done = count >= items.length;
      const value = done ? undefined : items[count++];
      return {
        value,
        done,
      }
    }
  }
}

ES5实现一个instanceOf

返回true的条件: 操作符右边(构造函数)的prototype对象出现在操作符左边(实例)的protptype chain(原型链)上。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const instanceOf2 = (target, construct) => {
	if (target === null || typeof target !== 'object') return false;
	let target__proto = Object.getPrototypeOf(instance);
	const construct__prototype = construct.prototype;

	while(target__proto) {
		if (target__proto === construct__prototype) {
			return true;
		}
		target__proto = Object.getPrototypeOf(target__proto);
	}
	return false;
}

__proto__是啥?

每个对象都有原型,原型是该对象的隐式引用,我们无法通过对象来直接访问原型。所幸JS提供了 Object.getPrototypeOf()让我们来获取对象的原型。不过有一些浏览器不知道抽什么风,在自己的浏览器通过__proto__ 来让对象直接访问原型。即: obj.__proto__。由于越来越多的浏览器实现了该功能,于是ECMA不得不将__proto__特性写入标准,文档规定了__proto__是存在于Object.prototype 的一个访问器属性,(BTW 访问器属性不在此解释),该属性的get函数里调用了Object.getPrototypeOf(),在set里调用了Object.setPrototypeOf(),用这种方式来达到自洽。所以当我们调用obj.__proto__时,底层实质上是在prototype chain(原型链)上寻找__proto__属性,大多数情况下都是在Object.prototype上找到(因为该属性其实可以被遮蔽),随即它的get函数被调用。

我们可以尝试实现一个与__proto__属性一样效果的属性:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const obj = {
	name: 'tim',

	get fake__proto__() {
		return Object.getPrototypeOf(this);
	},
	set fake__proto__(value) {
		Object.setPrototypeOf(this, value);
	}
}

console.log(obj.fake__proto__ === obj.__proto__);

由hasOwnProperty 得出的属性遮蔽

React源码中遍历config对象:

1
2
3
4
5
6
7
8
9
const hasOwnProperty = Object.prototype.hasOwnProperty;
for (propName in config) {
	if (
		hasOwnProperty.call(config, propName) &&
		!RESERVED_PROPS.hasOwnProperty(propName)
	) {
		props[propName] = config[propName];
	}
}

注意到并没有直接config.hasOwnProperty(propName),是因为原型链它存在属性遮蔽特性,也就是说,当顺着原型链往上找属性的时候,靠近实例的属性会先被找到,然后立刻停止寻找。所以React它无法保证你的config重没重写hasOwnProperty方法,所以他把真正的该方法(也就是Object.prototype上的方法)单独赋值给一个变量,然后再利用call指定上下文。

ES5实现 new 操作符?

new真正的操作:

  • 创建一个空对象
  • 指定该空对象的原型
  • 执行构造函数(需将构造函数的this指向创建的新对象)
  • 构造函数若返回一个对象,则返回那个对象,否则返回我们创建的新对象
1
2
3
4
5
6
7
const fakeNew = (construct, ...args) => {
	// 下面两行也可以用const obj = Object.create(contruct.prototype);
	const obj = {};
	Object.setPrototypeOf(obj, contruct.prototype);
	const result = construct.apply(obj, args);
	return typeof result === 'object' ? result : obj;
}

jsonp实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
(function(global) {
	// 用来标识成功回调函数的
	let id = 0;
	// 最后创建的script标签会被插入到head标签内
	const headNode = document.querySelector('head');

	function jsonp({options}) {
		// url为必填
		if (!options || !options.url ) return;

		const { data = {}, callback } = options;
		let url = options.url;
		const fnName = `jsonp${id++}`;
		data.callback = fnName;


		// 处理请求参数
		const urlParams = [];
		const hasOwnProperty = Object.prototype.hasOwnProperty;

		for(const key in data) {
			if (hasOwnProperty.call(data, key)) {
				urlParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`);
			}
		}
		url = url.indexOf('?') ? url + '&' : url + '?';
		url = url + urlParams.join('&');


		scriptNode.src = url;

		// 因为我们传给后端的callback = fnName,所以要把fnName暴露出去,不过不用担心,最终会被删除。
		global[fnName] = function(response) {
			callback && callback(response);
			headNode.removeChild(scriptNode);
			delete global[fnName];
		}

		scriptNode.onerror = function() {
			callback && callback({error: true});
			headNode.removeChild(scriptNode);
			delete global[fnName];
		}

		headNode.appendChild(scriptNode);
	}
	// 将jsonp暴露为全局函数
	global.jsonp = jsonp;
})(this);

ES5实现call

先将方法设置到上下文对象上,然后利用context.fn()的形式调用。 下面将数组依次传入到函数里用的是eval('context.fn(' + args + ')')方式,args会被转换成1,2,2,3这种形式,算是一个小收获!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Function.prototype.call = Function.prototype.call || function(context) {
	var context = context || window;
	context.fn = this;

	var args = [];
	for(var i = 0, len = arguments.length;i <len;i++) {
		// 不能直接args.push(arguments[i])这种,因为如果数组中有元素是对象的话,[].toString()会把对象转换成[Object object]
		args.push('arguments[' + i + ']');
	}

	var result = eval('context(' + args + ')');
	delete context.fn;

	return result;

}

实现flatten

利用递归来实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const flatten = (items) => {
	const isArray = Array.isArray;
	if (!isArray(items)) throw new Error('type error');
	const result = [];
	const recursiveArray = array => {
		for(const item of array) {
			if (isArray(item)) {
				recursiveArray(item)
			} else {
				result.push(item)
			}
		}
	}
	recursiveArray(items);
	return result;
}

上面的方式多余的result很烦,于是利用reduce函数简化如下:

1
2
3
4
5
6
const flatten = items => {
	if (!Array.isArray(items)) throw new Error(`the value you wanna flat isn't an array!`)
	return items.reduce((accumulation, current) => (
		accumulation.concat(Array.isArray(current) ? flatten(current) : current)
	), []);
}

再试试用生成器方式,里面主要使用了生成器委托,代表生成器里调用另一个生成器。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function *flatten(items) {
	for(const item of items) {
		if (Array.isArray(item)) {
			yield *flatten(item);
		} else {
			yield item;
		}
	}
}

const result = [...flatten([1,[3,4, [2, [3]]]])]

用reduce用上瘾了,来实现一个函数去重

1
2
3
4
const unique = (items, map = new Map()) => items.reduce((accumulation, current) => (
	accumulation.concat(map.has(current) ? [] : map.set(current, current).get(current))
), []);
const result = unique([1,1,1,3,2,2,3,4]); // [1,2,3,4]

sleep函数

1
2
3
4
5
6
7
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

const test = async () => {
	console.log(1);
	await sleep(3000);
	console.log(2);
}

bind的实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Function.prototype.bind = Function.prototype.bind || function fakeBind(context) {
	var outsideArgs = Array.prototype.slice.call(arguments, 1);
	var self = this;

	function fBound() {
		var insideArgs = Array.prototype.slice.call(arguments, 1);
		// bind返回的函数如果用new操作符调用 则bind的this失效
		return self.apply(this instanceOf fBound ? this : self, outsideArgs.concat(insideArgs));
	}

	// 如果不这么弄一下,那么bind返回的函数如果用new操作符调用生成的实例,(instanceOf 调用bind的函数)为false,不符合常理
	// 这里用Object.create而不是直接fBound.prototype === self.prototype 因为:
	// 如果我们bind返回一个fn,fn.prototype.a = 111;就会也把调用bind的函数改了,所以要用Object.create。
	fBound.prototype = Object.create(self.prototype);
	return fBound;
}

解释一下DNS

你好,首先,DNS的全称是Domain Namespace System,翻译过来是域名命名空间系统。由于我们人类记忆数字的能力天生就不是很强,但是我们基于TCP/IP的互联网中的各个节点(服务器、路由器)都是通过IP地址来标识身份的,IP由32位比特组成,每8个比特用一个.分隔开,且用十进制数字标识。所以我们发明域名-IP的这样一个映射关系,我们只需要关心域名,而域名对应的IP则由我们的DNS来提供。

那DNS的细节是什么呢?首先,我们编写的程序,一般都会把DNS查询的操作委托给操作系统来做,具体一点呢,就是socket库,socket提供了很多操作功能。我们一般会在程序源码中见到这样的代码:

ip = gethostbyname('www.google.com');
updatedupdated2020-07-082020-07-08