【JS】原生JS封装各种常用方法、浏览器兼容方法

IE 不支持 ES6 语法,请使用相关 js 使其支持 ES6 语法,或手动将 ES6 语法修改。

本文内容不完善、错误之处,希望大家能够及时指出,万分感激。

最后一次编辑于 2019 年 07 月 08 日 10: 07。

1、继承 inherit

需求
  1. 能够实现 Target (A) 继承自 Origin (B)。
  2. 直接修改 Target (A) 原型(prototype) 上的属性,对 Origin (B) 不影响。
  3. 通过某种方法修改 Target (A) 原型上的属性,能够对 Origin (B) 产生影响。
知识点
  1. new 出来的对象的 __proto__ 属性完全等同于构造方法的 prototype
    · 例: var a = new A();a.__proto__ 完全等同于 A.prototype
  2. 只有构造方法的 prototype 上的属性才能被继承使用,构造方法内的 this.xxx 不会被继承。
实现
// 方法
var inherit = (() => {
	let F = function () {}
	return (Target, Origin) => {
		F.prototype = Origin.prototype;
		Target.prototype = new F();
		Target.prototype.constructor = Target;	// 复位,不然指向了 Origin
		Target.prototype.uber = Origin.prototype	;	// 用 uber 属性表示继承自谁
	}
})();
测试
// 注:本代码虽写到一起,但真实测试是分开写的,console.log() 方法后面没有语句
function A(){this.a = 0;}
function B(){this.b = -1;}
console.log(A.prototype);
// 因为 A 的原型就是 Object
// 打印 ↓ {constructor: f}
//		 → constructor: f A()
//		 → _proto___: Object

console.log(B.prototype);
// 因为 B 的原型就是 Object
// 打印 ↓ {constructor: f}
//		 → constructor: f B()
//		 → _proto___: Object

A.prototype.a = 1;
A.prototype.b = 2;
console.log(A.prototype);
// 因为 A 的原型就是 Object,相当于给 Object 加了两个属性
// 打印 ↓ {a: 1, b: 2, constructor: f}
//		   a: 1
//		   b: 2
//		 → constructor: f A()
//		 → _proto___: Object

console.log(B.prototype);	// B 没动,同上一个 console.log(B.prototype);
console.log(A.prototype.constructor);	// 打印 f A(){this.a = 0;}
console.log(B.prototype.constructor);	// 打印 f B(){this.b = 0;}

inherit(A,B);
console.log(A.prototype);
// 因为修改了继承,A 的原型变成 B
// 打印 ↓ B {constructor: f, uber: {...}}
//		 → constructor: f A()
//		 ↓ uber:
//		   → constructor: f B()
//		   → _proto___: Object
//		 → _proto___: Object

console.log(B.prototype);	// B 没动,同上一个 console.log(B.prototype);
console.log(A.prototype.uber);	// 同 console.log(B.prototype);
console.log(A.prototype.uber.constructor);	// 打印 f B(){this.b = 0;}
console.log(A.prototype.constructor);	// 打印 f A(){this.a = 0;}
console.log(B.prototype.constructor);	// 打印 f B(){this.b = 0;}

var a = new A();
var b = new B();
console.log(a.a);			// 打印 0	因为 A 的构造函数里有 a 属性
console.log(a.b);			// 打印 undefined	因为 A 的构造函数里没有 b 属性, 且 A 的原型变了,是  B,同样没有 b 属性
console.log(a.__proto__.a);	// 打印 undefined	因为 A 的原型变了,是  B ,没有 a 属性
console.log(a.__proto__.b);	// 打印 undefined	因为 A 的原型变了,是  B ,没有 b 属性

A.prototype.a = 3;
A.prototype.b = 4;
console.log(A.prototype);
// 打印 ↓ B {constructor: f, uber: {...}, a: 3, b: 4}
//		   a: 3
//		   b: 4
//		 → constructor: f A()
//		 ↓ uber:
//		   → constructor: f B()
//		   → _proto___: Object
//		 → _proto___: Object

console.log(B.prototype);	// B 没动,同上一个 console.log(B.prototype);
console.log(a.a);			// 打印 0	因为 A 的构造函数里有 a 属性
console.log(a.b);			// 打印 4	因为 A 的构造函数里没有 b 属性,于是去原型上找
console.log(a.__proto__.a);	// 打印 3	相当于 console.log(A.prototype.a)
console.log(a.__proto__.b);	// 打印 4	相当于 console.log(A.prototype.b)
console.log(b.a);			// 打印 undefined	因为 B 的构造函数里没有 a 属性
console.log(b.b);			// 打印 -1	因为 B 的构造函数里有 b 属性

A.prototype.uber.c = 5;		// 这里动的其实是 B 的原型,因为 A.prototype.uber === B.prototype
console.log(a.c);			// 打印 5	因为 A 的构造函数里没有 c 属性,于是去原型上找,A 的原型上没有,于是去 B 的原型上找,找到了
console.log(b.c);			// 打印 5	因为 B 的构造函数里没有 c 属性,于是去原型上找
var c = new B();
console.log(c.c);			// 打印 5	影响之后 new 出来的属性

2、更为精确的 typeof

实现
/**
 * 更为精确的 typeof
 * @return 可能值为 object、array、numberObject、booleanObject、stringObject、
 * 				   null、undefined、function、number、boolean、string
 */
var type = (() => {
	return function(target){
		let template = {
			"[object Object]": "object",
			"[object Array]": "array",
			"[object Number]": "numberObject",
			"[object Boolean]": "booleanObject",
			"[object String]": "stringObject"
		}
		if (target === null){
			return "null";
		} else if (typeof(target) === "object"){
			return template[Object.prototype.toString.call(target)];
		} else {
			return typeof(target);
		}
	}
})();

3、数组去重

需求
  1. 能够实现对一个基本数据类型的数组内重复元素进行剔除,保证数组内元素唯一性。
  2. 不修改原数组。
实现
// 写在 Array 原型链上,方便数组直接调用,同时保证安全性
Array.prototype.unique = (() => {
	return function() {
		let obj = {}, result = [], len = this.length;
		for (let i = 0; i < len; i++) {
			let item = this[i];
			if (!obj[item]) {
				obj[item] = "a";
				result[result.length] = item;
			}
		}
		return result;
	}
})();
测试
var a = [1, 2, 4, 5, 2, 1, 5, 78, 12, 3, 45, 1, 2, 45, 1];
console.log(a);	// 打印 (15) [1, 2, 4, 5, 2, 1, 5, 78, 12, 3, 45, 1, 2, 45, 1]
var b = a.unique();
console.log(a);	// 打印 (15) [1, 2, 4, 5, 2, 1, 5, 78, 12, 3, 45, 1, 2, 45, 1]
console.log(b);	// 打印 (8) [1, 2, 4, 5, 78, 12, 3, 45]

4、手写 children 方法(兼容IE)

知识点
  1. IE5 到 IE8 完全支持 children 属性,与 IE9 以上版本无异。
  2. IE5 到 IE8 完全支持 childNodes 属性,但返回值的每个元素与 children 属性返回值的每个元素一致。
// 测试,IE5 - IE 8环境,body下有 5 个子元素,分别是 div 、script 等
console.log(document.body.childNodes.length === document.body.children.length);
for(let i = 0; i < document.body.childNodes.length; i++) {
	console.log(i, document.body.childNodes[i] === document.body.children[i]);
}
// 打印结果如下
//	true
//	1 true
//	2 true
//	3 true
//	4 true
  1. IE9 以上版本的 childNodes 属性才返回全部类型子节点。
  2. nodeType 值含义如下:
含义
元素节点1
属性节点2
文本节点3
注释节点8
document9
DocumentFragemnt11
实现
var returnChildren = (() => {
	return node => {
		let result = {
				length: 0,
				push: Array.prototype.push,
				splice: Array.prototype.splice
			},
			chilNodes = node.childNodes,
			len = childs.length;
		for(let i = 0; i < len; i++) {
			let child = childNodes[i]
			if(child.nodeType === 1) {
				result[result.length] = child;
			}
		}
		return result;
	}
})();

5、查找第一个或最后一个子元素、前一个或后一个兄弟元素(兼容IE)

思路
  • 利用 nodeType 值来查找。
知识点
  • 全浏览器支持 parentNode 和 parentElement ,他们的区别在于 html 元素的这个属性是 document 还是 null。
// 测试
console.log(document.getElementsByTagName("html")[0].parentNode);
// 打印	→ #document
console.log(document.getElementsByTagName("html")[0].parentElement);
// 打印	null
实现
// 第一个子元素
var firstChild = (() => {
	return parentNode => {
		return returnChildren(parentNode)[0];
		// 或 parentNode.children[0]
	}
})();

// 最后一个子元素
var lastChild = (() => {
	return parentNode => {
		let children = returnChildren(parentNode);
		return children[children.length - 1];
		// 或 parentNode.children[parentNode.children.length - 1]
	}
})();

// 前一个兄弟元素
var previousElementSibling = (() => {
	return node => {
		let result = node.previousSibling;
		while(result && result.nodeType !== 1){
			result = result.previousSibling;
		}
		return result;
	}
})();
// 注:IE 8 及以下版本 previousElementSibling(document.body) === document.head 返回 false,因为 document.head 是 null
//     IE 8 及以下版本 previousElementSibling(document.body) === document.getElementsByTagName("head")[0] 返回 true
//     IE 9 及以上版本和其他浏览器 previousElementSibling(document.body) === document.head 返回 true
//     previousElementSibling(document.head) 返回 null

// 后一个兄弟元素
var nextElementSibling = (() => {
	return node => {
		let result = node.nextSibling;
		while(result && result.nodeType !== 1){
			result = result.nextSibling;
		}
		return result;
	}
})();

6、手写 DOM 元素的 inserAfter 方法(兼容IE)

思路
  • 利用 inserBefore 和 appendChild 方法。
知识点
  • inserBefore 方法是 Node.prototype 上的方法。
实现
// 未完待续(累了,下次写)

7、求屏幕滚动距离(兼容IE)

实现
var getScrollOffset = (() => {
	return () => {
		if (window.pageXOffset) {
			// 非 IE
			return {
				x: window.pageXOffset,
				y: window.pageYOffset
			}
		} else {
			// IE
			return {
				x: document.body.scrollLeft + document.documentElement.scrollLeft,
				y: document.body.scrollTop + document.documentElement.scrollTop
			}
		}
	}
})();

8、求屏幕可视区域宽高(兼容IE)

实现
var getWindowSize = (() => {
	return () => {
		if(window.innerWidth){
			// 非 IE
			return {
				w: window.innerWidth,
				h: window.innerHeight
			}
		} else if (document.compatMode === "CSS1Compat") {
			// 标准模式
			return {
				w: document.documentElement.clientWidth,
				h: document.documentElement.clientHeight
			}
		} else // if (document.compatMode === "BackCompat")
			// 混杂模式
			return {
				w: document.body.clientWidth,
				h: document.body.clientHeight
			}
	}
})();

9、阻止事件冒泡和默认事件(兼容IE)

实现
// 阻止冒泡
var eventStopPropagation = (() => {
	return e => {
		e = e || window.event;
		if (e.stopPropagation)
			e.stopPropagation();
		else
			e.cancelBubble = true;
	}
})();

// 阻止默认事件
var eventPreventDefault = (() => {
	return e => {
		e = e || window.event;
		if (e.preventDefault)
			e.preventDefault();
		else
			e.returnValue = true;
	}
})();

10、按需加载 script(兼容IE)

实现
var loadScript = (() => {
	return (url, fn) => {
		let s = document.createElement("script");
		s.type = "text/javascript";
		if (s.readyState) {	// IE
			s.onreadystatechange = function() {
				if (this.readyState === "complete" || this.readyState === "loaded") {
					fn();
				}
			}
		} else {
			s.onload = fn;
		}
		s.src = url;
		if (document.head) {
			document.head.appendChild(s);
		} else {
			document.getElementsByTagName("head")[0].appendChild(s);
		}
	}
})();

win7 都停止更新了,IE 什么时候彻底弃用啊?IDEA 都提示 window.event 方法被弃用了。。。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值