原生JS封装各种常用方法、浏览器兼容方法
IE 不支持 ES6 语法,请使用相关 js 使其支持 ES6 语法,或手动将 ES6 语法修改。
本文内容不完善、错误之处,希望大家能够及时指出,万分感激。
最后一次编辑于 2019 年 07 月 08 日 10: 07。
1、继承 inherit
需求
- 能够实现 Target (A) 继承自 Origin (B)。
- 直接修改 Target (A) 原型(prototype) 上的属性,对 Origin (B) 不影响。
- 通过某种方法修改 Target (A) 原型上的属性,能够对 Origin (B) 产生影响。
知识点
new
出来的对象的__proto__
属性完全等同于构造方法的prototype
。
· 例:var a = new A();
则a.__proto__
完全等同于A.prototype
。- 只有构造方法的
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、数组去重
需求
- 能够实现对一个基本数据类型的数组内重复元素进行剔除,保证数组内元素唯一性。
- 不修改原数组。
实现
// 写在 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)
知识点
- IE5 到 IE8 完全支持 children 属性,与 IE9 以上版本无异。
- 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
- IE9 以上版本的 childNodes 属性才返回全部类型子节点。
- nodeType 值含义如下:
含义 | 值 |
---|---|
元素节点 | 1 |
属性节点 | 2 |
文本节点 | 3 |
注释节点 | 8 |
document | 9 |
DocumentFragemnt | 11 |
实现
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 方法被弃用了。。。