面向对象
构造函数 VS 普通函数
/*
* 编程语言
* 面向对象 OOP java、javascript、php、C#(ASP.NET)、Python、GO、C++、Ruby...
* 面向过程 POP C
* 标记语言:HTML / CSS
*
* =====面向对象编程「对象、类、实例」
* 对象:万物皆对象(泛指)
* 类:对“对象”的划分(按照其功能结构特点,划分出大类和小类)
* 实例:类中具体的事务
*
* JS本身就是基于面向对象思想开发出来的编程语言,所以我们学习和开发JS的时候,也要按照面向对象的思想去处理!!
* 「内置类」
* + 每一种数据类型都有一个自己所属的内置类:Number数字类(每一个数字/NaN/Infinity都是它的实例)、String、Boolean、Symbol、BigInt、Array、RegExp、Date、Function、Object...
* + 每一种DOM元素也都有自己所属的类:
* window -> Window -> WindowProperties -> EventTarget -> Object
* document -> HTMLDocument -> Document -> Node -> EventTarget -> Object
* div -> HTMLDivElement -> HTMLElement -> Element -> Node -> ...
* a -> HTMLAnchorElement -> HTMLElement -> ...
* + HTMLCollection / NodeList / CSSStyleDeclaration / DOMTokenList ...
* + ....
* 学习数组,首先分析一个数组(实例),研究清楚这个实例的特征后(含:结构特点和常用方法等),我们再遇到其他的数组,直接也是按照相同的机制进行处理的
*
* 「自定义类」
* 创建一个函数 fn
* + fn() 普通函数执行「堆栈机制」
* + new fn() 构造函数执行 「堆栈机制 + 面向对象机制」
*/
function Fn(x, y) {
let total = x + y;
this.x = x;
this.y = y;
}
let result = new Fn(10, 20);
console.log(result);
复制代码
对象遍历问题及解决方案
function Fn() {
/*
* EC(FN)
* 初始创建Fn找个类的一个实例对象 0x000
* 初始THIS:this->0x000
*/
let total = 0; //上下文的私有变量 和实例对象没有必然的联系
this.x = 10; //this.xxx=xxx 都是给实例对象设置的私有属性和方法
this.y = 20;
this.say = function () { //0x000.say=0x100 0x001.say=0x101
console.log('SAY');
};
/* 如果不设置返回值,或者返回值是一个基本类型值,默认都会把实例对象 0x000 返回;如果手动返回的是一个引用数据类型值,则以自己返回的为主; */
// return {
// name: 'zhufeng'
// };
}
let f1 = new Fn(); //->0x000
let f2 = new Fn; //->0x001 new执行的时候,如果类不需要传递实参,可以不用加小括号(不加小括号,叫做无参数列表new;设置小括号,叫做带参数列表new;除了是否传递参数的区别,在运算的优先级上也有区别? new Fn->19 new Fn()->20)
// 每一次new都是把函数重新执行(重新形成一个新的私有上下文、重新创建一个实例对象、代码重新执行...)
// console.log(f1, f2, f1 === f2); //=>false
// 检测某个成员(属性/键)是否属于这个对象,或者是否属于这个对象的私有属性
// in:检测成员是否属于这个对象「特点:不论是私有属性,还是公有的属性,只要有则检测结果就是true」
// hasOwnProperty:用来检测当前成员是否为对象的私有属性「特点:只有是私有属性,结果才是ture,哪怕有这个属性,但是属于公有的属性,结果也是false」
// console.log(f1);
// console.log('say' in f1); //->true
// console.log(f1.hasOwnProperty('say')); //->true
// f1是一个对象,他可以访问hasOwnProperty方法并且执行,说明:‘hasOwnProperty’属性是它的一个成员
// console.log('hasOwnProperty' in f1); //->true
// console.log(f1.hasOwnProperty('hasOwnProperty')); //->false 说明‘hasOwnProperty’不是它的私有属性,也就是它的公有属性「前提基于in检测出来的结果是true」
// obj:要检测的对象
// attr:要验证的成员
function hasPubProperty(obj, attr) {
// 思路一:是它的属性 但是还不是私有的,那么一定是公有的「BUG:如果某个属性即是私有的,也是公有的,则检测出来的结果是不准确的」
// return (attr in obj) && (!obj.hasOwnProperty(attr));
// 思路二:真正的思路应该是检测原型上的属性,因为原型上的属性都是公有的
// Object.getPrototypeOf:获取当前对象的原型
let proto = Object.getPrototypeOf(obj);
while (proto) {
// 依次查找原型链,直到找到Object.prototype为止
if (proto.hasOwnProperty(attr)) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
// let sy = Symbol();
// let obj = {
// name: 'zhufeng',
// age: 12,
// 3: '哈哈哈',
// 0: 'zhouxiaotian',
// [sy]: 100
// };
/* console.log(obj);
console.log(obj.hasOwnProperty('name'));
console.log(obj.hasOwnProperty(sy)); //->hasOwnProperty是可以检测Symbol属性的
console.log(sy in obj); //->in也是可以检测Symbol属性的 */
// 很多对“对象”的操作是无法拿到Symbol属性的
// Object.prototype.AAA = 100; //->‘AAA’是obj公共的属性 obj.hasOwnProperty('AAA')->false 'AAA' in obj->true
/* for (let key in obj) {
console.log(key); //->‘name’ ‘AAA’
// for in遍历的时候
// + 无法遍历Symobol的私有属性
// + 但是可以遍历到自己扩展的公共属性「内置的公共属性是不可枚举的(就是无法遍历到的)」
// + 优先遍历数字属性,而且按照从小到大(不会严格按照属性书写的顺序)
} */
/* // 解决:能够避免遍历公共的
for (let key in obj) {
if (!obj.hasOwnProperty(key)) break; //已经遍历到公共的,则私有已经遍历完,结束循环
console.log(key); //->'name'
} */
/* // 解决:只想遍历私有的,包含Symbol的
// Object.keys:获取一个对象非Symbol的私有属性(结果是一个数组,数组中包含获取的属性)
// 类似的还有:Object.getOwnPropertyNames
// Object.getOwnPropertySymbols:只获取Symbol的私有属性(结果也是一个数组)
let keys = [
...Object.getOwnPropertyNames(obj),
...Object.getOwnPropertySymbols(obj)
];
keys.forEach(key => {
console.log(key, obj[key]);
}); */
复制代码
原型和原型链
原型重定向
function Fn() {}
Fn.prototype.x = 100;
Fn.prototype.y = 200;
/* // 缺少constructor && 原始原型对象上的x/y也丢失了
Fn.prototype = {
// getX:function() {}
getX() {},
getY() {}
}; */
/* let proto = {
// 手动设置的constructor是属于可枚举的
constructor: Fn,
getX() {},
getY() {}
};
Fn.prototype = Object.assign({}, Fn.prototype, proto); //->这样合并,最后返回的是一个全新的对象,由于内置的Fn.prototype中的constructor是内置的不可枚举的属性,所以合并后也是无法赋给新对象的 */
/* Fn.prototype = Object.assign(Fn.prototype, {
getX() {},
getY() {}
}); //->这种合并的办法,Fn.prototype还是之前的堆地址,只不过是把新对象中的内容全部扩展到了原始的堆中 */
/*
let obj1 = {
x: 100,
y: 200,
n: {
0: 1,
1: 2
}
};
let obj2 = {
y: 300,
z: 400,
n: {
name: 'zhufeng'
}
};
// Object.assign:合并两个对象「浅比较」
// + 让obj2中的内容替换obj1中的:两者都有的以obj2为主,只有其中一个具备的都是相当于新增...
// + 最后返回的是obj1对象的堆内存地址「相当于改变的是obj1对象中的内容」,并不是返回一个全新的对象...
// let obj = Object.assign(obj1, obj2);
// console.log(obj === obj1); //true
// let obj = Object.assign({}, obj1, obj2);
// console.log(obj); //->全新的对象,也就是assign的第一个参数「新对象」
// console.log(Object.assign(obj1, obj2)); //->浅比较:obj2.n直接覆盖obj1.n
*/
/* let obj = {
fn1() {},
fn2: function fn2() {}
// 两者写法的区别:
// + 第一种写法:obj.fn1函数是没有prototype属性的 「不能被作为构造函数」
// + 第二种写法:和正常的函数没有区别
};
new obj.fn1(); //Uncaught TypeError: obj.fn1 is not a constructor */
复制代码
内置类原型扩展方法
/*
* 向内置类的原型扩展方法
* + 内置类的原型上提供了很多内置方法,但是这些方法不一定完全满足业务需求,此时需要我们自己扩展一些方法
* 「优势」
* + 调用起来方便
* + 可以实现链式写法
* + 限定调取方法的类型,必须是指定类的实例
* + 扩展的方法,各个模块「其他成员」都可以直接的调用
* + ...
* 「弊端」
* + 自己扩展的方法,容易覆盖内置的方法 (解决:自己设定的方法名要设置前缀 myUnique)
* Array.prototype={...} 这样操作是无效的,也怕你一行代码,把数组方法全干没了
* + 基于for in遍历的时候,会把自己扩展到原型上的方法也遍历到
* + ...
*/
/*
function unique(arr) {
let obj = {};
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (obj.hasOwnProperty(item)) {
// 数组之前出现过这一项,当前项就是重复的,我们此时删除当前项即可
arr.splice(i, 1);
i--;
continue;
}
obj[item] = item;
}
return arr;
}
let arr = [10, 30, 40, 20, 40, 30, 10, 40, 20];
arr = unique(arr);
*/
Array.prototype.unique = function unique() {
// this:一般都是当前要操作的实例(也就是要操作的数组)
let obj = {},
self = this;
for (let i = 0; i < self.length; i++) {
let item = self[i];
if (obj.hasOwnProperty(item)) {
// 数组之前出现过这一项,当前项就是重复的,我们此时删除当前项即可
self.splice(i, 1);
i--;
continue;
}
obj[item] = item;
}
return self; //实现链式写法
};
let arr = [10, 30, 40, 20, 40, 30, 10, 40, 20];
arr.unique().sort((a, b) => a - b).reverse().push('zhufeng'); //执行完成sort返回的是排序后的数组(原始数组也是变的)... 执行完成push返回的是新增后数组的长度「不能再调数组方法了」 => “链式写法”:执行完成一个方法,返回的结果是某个实例,则可以继续调用这个实例所属类原型上的方法...
console.log(arr);
复制代码
重写内置new
/*
function Fn() {
// 创建一个实例对象
// ---- 也会像普通函数执行一样,让其执行 「THIS指向实例对象」
// 返回值没有或者是基本值,则返回的是实例对象...
}
let f1 = new Fn;
*/
// 分析内置new的原理,重写一下
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function () {
console.log('wangwang');
}
Dog.prototype.sayName = function () {
console.log('my name is ' + this.name);
}
function _new(Ctor, ...params) {
// 1.创建一个实例对象「创建Ctor类的实例:实例.__proto__ -> 类.prototype」
/* let obj = {};
obj.__proto__ = Ctor.prototype; */
let obj = Object.create(Ctor.prototype);
// 2.把函数执行「THIS指向实例对象」 call->执行函数,改变函数中的THIS
let result = Ctor.call(obj, ...params);
// 3.处理返回值
if (result !== null && /^(object|function)$/.test(typeof result)) return result;
return obj;
}
let sanmao = _new(Dog, '三毛');
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 三毛"
console.log(sanmao instanceof Dog); //=>true
// ----https://www.caniuse.com/
// Object.create([proto]):创建一个空对象,并且让创建的这个空对象的.__proto__指向[proto] “把[proto]作为创建对象的原型”
// let obj = Object.create(); //Uncaught TypeError: Object prototype may only be an Object or null
// let obj = Object.create(Dog.prototype);
// let obj = Object.create(null); //->创建一个空对象,并且阻止了他的__proto__指向「没有这个属性了」
// console.log(obj);
复制代码
函数的多种角色
JQ源码分析
工厂模式
const jquery = require("./jquery");
function factory(window, noGlobal) {
// window->window noGlobal->undefined
var arr = [];
var slice = arr.slice; //Array.prototype.slice
var version = "3.5.1",
jQuery = function (selector, context) {
return new jQuery.fn.init(selector, context);
};
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
// 把JQ对象转换为原生对象
get: function (num) {
if (num == null) {
// num=null/undefined
return slice.call(this);
}
return num < 0 ? this[num + this.length] : this[num];
},
// 基于索引 最后返回的依然是实例对象
eq: function (i) {
var len = this.length,
j = +i + (i < 0 ? len : 0);
// this[j] 原生的,包在数组中
return this.pushStack(j >= 0 && j < len ? [this[j]] : []);
},
pushStack: function (elems) {
// this.constructor->jQuery jQuery()空JQ实例
// JQ对象:{0:xxx,length:1}
var ret = jQuery.merge(this.constructor(), elems);
ret.prevObject = this;
return ret;
},
each: function (callback) {
// $(...).each(callback)
// this:JQ实例(类数组JQ对象)
return jQuery.each(this, callback);
},
};
jQuery.each = function each(obj, callback) {
var length, i = 0;
// isArrayLike:检测是否为数组或者类数组
if (isArrayLike(obj)) {
length = obj.length;
for (; i < length; i++) {
//每一轮循环都去执行回调函数
// + 传递实参:索引/当前项
// + 改变THIS:当前项
// + 接收返回值:如果回调函数返回false,则结束循环
var result = callback.call(obj[i], i, obj[i]);
if (result === false) {
break;
}
}
} else {
// 对象
/* for (i in obj) {
// for in遍历的问题:
// + 1.遍历到原型上自己扩展的公共的属性
// + 2.顺序
// + 3.无法找到symbol的属性
if (callback.call(obj[i], i, obj[i]) === false) {
break;
}
} */
var keys = Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj));
for (; i < keys.length; i++) {
var key = keys[i];
if (callback.call(obj[key], key, obj[key]) === false) {
break;
}
}
}
return obj;
}
var rootjQuery = jQuery(document);
var rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;
var init = jQuery.fn.init = function (selector, context, root) {
var match, elem;
// HANDLE: $(""), $(null), $(undefined), $(false)
// 返回结果是一个JQ实例「空的实例对象」
if (!selector) {
return this;
}
// $('.xxx') => root=$(document)
root = root || rootjQuery;
// 选择器是一个字符串?
if (typeof selector === "string") {
if (selector[0] === "<" &&
selector[selector.length - 1] === ">" &&
selector.length >= 3) {
match = [null, selector, null];
} else {
match = rquickExpr.exec(selector);
}
// Match html or make sure no context is specified for #id
if (match && (match[1] || !context)) {
// HANDLE: $(html) -> $(array)
if (match[1]) {
context = context instanceof jQuery ? context[0] : context;
// Option to run scripts is true for back-compat
// Intentionally let the error be thrown if parseHTML is not present
jQuery.merge(this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
));
// HANDLE: $(html, props)
if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
for (match in context) {
// Properties of context are called as methods if possible
if (isFunction(this[match])) {
this[match](context[match]);
// ...and otherwise set as attributes
} else {
this.attr(match, context[match]);
}
}
}
return this;
} else {
elem = document.getElementById(match[2]);
if (elem) {
// Inject the element directly into the jQuery object
this[0] = elem;
this.length = 1;
}
return this;
}
// HANDLE: $(expr, $(...))
} else if (!context || context.jquery) {
return (context || root).find(selector);
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
return this.constructor(context).find(selector);
}
// HANDLE: $(DOMElement)
} else if (selector.nodeType) {
// 选择器是一个节点「DOM元素节点/文本节点... JS获取的」
this[0] = selector;
this.length = 1;
return this;
} else if (isFunction(selector)) {
// 选择器是一个函数 $(document).ready(函数) 「监听DOMContentLoaded事件:等到DOM结构加载完成,执行对应的方法」
return root.ready !== undefined ?
root.ready(selector) :
selector(jQuery);
}
return jQuery.makeArray(selector, this);
};
init.prototype = jQuery.fn;
// 浏览器环境下运行,条件成立的
if (typeof noGlobal === "undefined") {
window.jQuery = window.$ = jQuery;
}
}
factory(window);
//===========
// $() -> 就是把jQuery方法执行的「普通函数」 “JQ选择器”
// =>最后获取的结果是jQuery类的实例对象“JQ对象”
// $('.box')
// $('.box',conatiner)
/* $('.box')
jQuery('.box')
$.ajax({}); */
//...
//=> $(document).ready(函数)
/* $(function () {
// 等待页面中的DOM结构渲染完,去执行回调函数
// ...
}); */
// 基于JS方法获取的是原生DOM对象:可以调用内置的JS方法
// 基于$()获取的JQ对象,只能调JQ原型上的方法
// ===默认两种对象之间所用的方法不能混着调,想调用只能先相互转换
// 原生->JQ $(原生对象) {0:xxx,length:1...} 「类数组集合」
// JQ->原生 $xxx[索引] / $xxx.get(索引)
复制代码
JS四种数据类型检测
/*
* JS中的数据类型检测都有哪些办法?
* + typeof [value]
* + 简单方便,大部分数据类型都已有效检测出来
* + typeof null ->"object" JS设计的缺陷:数据值都是按照二进制存储的 1整数 010浮点数 100字符串 110布尔 000对象 -2^30undefined 000000null ... =>也说明了typeof检测数据类型是按照二进制存储的值进行检测的
* + typeof不能细分具体的对象数据类型值,所有对象数据类型的值,检测出来的结果都是"object"
* + typeof检测基于构造函数创建出来的,基本数据类型的实例对象,结果也是"object"
*
* + Object.prototype.toString.call([value])
* + 万全之策
* + 大部分内置类的原型上都有toString,但是一般都是转换为字符串,只有Object.prototype上的toString并不是转换为字符串,而是返回当前实例对象所属类的信息的 “[object 所属构造函数的信息]”
* + 所属构造函数的信息是根据 Symbol.toStringTag 获取的「有这个属性基于这个获取,没有浏览器自己计算」
* let obj={name:'zhufeng'};
* obj.toString -> Object.prototype.toString
* let arr=[];
* arr.toString -> Array.prototype.toString
* 鸭子类型「原型上方法的借用」
* =>Object.prototype.toString.call(arr)
* =>({}).toString.call(arr)
*
* + instanceof
* + 检测某个实例是否属于这个类的
* + 基于instanceof可以细分一下不同类型的对象「也可以检测出基于构造函数方式创建出来的基本类型对象值」
* + 临时当“壮丁”的,存在很多问题
* + 原理:构造函数[Symbol.hasInstance](实例)
* + 原理:检测当前构造函数的原型prototype是否出现在,当前实例所处的原型链上__proto__,如果能出现结果就是true
* + 在JS中原型链是可以改动的,所有结果不准确
* + 所有实例的原型链最后都指向Object.prototype,所以 “实例 instacnceof Object”的结果都是true
* + 字面量方式创造的基本数据类型值是无法基于 instanceof 检测的「浏览器默认并不会把它转换为new的方式」,所以它本身不是对象,不存在__proto__这个东西
* + ...
*
* + constructor
* + 临时当“壮丁”的,也存在很多问题
* + constructor是可以肆意被修改,所以也不准
*/
/* typeof
// JS中创建一个值有两种方案:
// 1.字面量方式
let n = 100;
let obj1 = {};
// 2.构造函数方式 「不能 new Symbol/new BigInt -> Object(symbol/bigint) 其他基本类型值也可以这样处理,但是都要排除null/undefined」
let m = new Number(100);
let obj2 = new Object();
// 对于基本数据类型,两种方式的结果是不一样的:
// 字面量方式得到的是基本数据类型「特殊的实例」,而构造函数方式得到的是对象类型「正规的实例」
// 对于引用数据类型,两种方式除了语法上的一些区别,没有本质的区别,获取的都是对应类的实例对象
*/
/*
let arr = [10, 20];
let obj = {
0: 10,
1: 20,
length: 2
};
let m = new Number(100);
let n = 100;
console.log(arr instanceof Array); //->true
console.log(arr instanceof Object); //->true
console.log(obj instanceof Array); //->false
console.log(m instanceof Number); //->true
console.log(n instanceof Number); //->false
*/
/*
class Fn {
static[Symbol.hasInstance]() {
console.log('OK');
return false;
}
}
let f = new Fn;
console.log(f instanceof Fn);
*/
/*
function Fn() {}
Fn.prototype = Array.prototype;
let f = new Fn;
console.log(f instanceof Array);
*/
/*
let arr = [10, 20];
let obj = {
0: 10,
1: 20,
length: 2
};
let n = 100;
let m = new Number(100);
console.log(arr.constructor === Array); //->true
console.log(obj.constructor === Array); //->false
console.log(arr.constructor === Object); //->false
console.log(n.constructor === Number); //->true
console.log(m.constructor === Number); //->true
*/
/* let class2type = {};
let toString = class2type.toString; //=>Object.prototype.toString
console.log(toString.call(1));
console.log(toString.call(new Number(1)));
console.log(toString.call('zhufeng'));
console.log(toString.call(true));
console.log(toString.call(null));
console.log(toString.call(undefined));
console.log(toString.call([10, 20]));
console.log(toString.call(/^\d+$/));
console.log(toString.call({}));
console.log(toString.call(function () {})); */
/* function* fn() {}
console.log(Object.prototype.toString.call(fn)); //->"[object GeneratorFunction]" */
/*
function Fn() {}
Fn.prototype[Symbol.toStringTag] = 'Fn';
let f = new Fn;
console.log(Object.prototype.toString.call(f)); //->“[object Fn]”
*/
/*
let arr = [];
console.log(Array.isArray(arr)); //->true
console.log(Object.prototype.toString.call(arr) === "[object Array]"); //->true
console.log(/array/i.test(Object.prototype.toString.call(arr))); //->true
*/
复制代码
JQ数据类型检测方法封装
(function () {
var class2type = {};
var toString = class2type.toString; //Object.prototype.toString 检测数据类型的
var hasOwn = class2type.hasOwnProperty; //Object.prototype.hasOwnProperty 检测是否私有属性的
var fnToString = hasOwn.toString; //Function.prototype.toString 把函数转换为字符串
var ObjectFunctionString = fnToString.call(Object); //=>"function Object() { [native code] }"
var getProto = Object.getPrototypeOf; //获取当前对象的原型链__proto__
// 建立数据类型检测的映射表 { "[object Array]":"array",....}
var mapType = ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object", "Error", "Symbol", "BigInt"];
mapType.forEach(function (name) {
class2type["[object " + name + "]"] = name.toLocaleLowerCase();
});
// 检测数据类型的办法
var toType = function toType(obj) {
if (obj == null) {
// 传递的是 null/undefined
return obj + "";
}
// 基于字面量方式创造的基本数据类型,直接基于typeof检测即可「性能要高一些」;
// 剩余的基于Object.prototype.toString.call的方式来检测,把获取的值到映射表中匹配,匹配结果是字符串对应的数据类型;
return typeof obj === "object" || typeof obj === "function" ?
class2type[toString.call(obj)] || "object" :
typeof obj;
};
// 检测是否为函数
var isFunction = function isFunction(obj) {
// typeof obj.nodeType !== "number" :防止在部分浏览器中,检测<object>元素对象结果也是"function",但是它的nodeType=1,处理浏览器兼容问题
return typeof obj === "function" && typeof obj.nodeType !== "number";
};
// 检测是否为window对象
var isWindow = function isWindow(obj) {
// window.window===window 符合这个条件的就是window对象
return obj != null && obj === obj.window;
};
// 检测是否为数组或者类数组
var isArrayLike = function isArrayLike(obj) {
// length存储的是对象的length属性值或者是false
// type存储的是检测的数据类型
var length = !!obj && "length" in obj && obj.length,
type = toType(obj);
// window.length=0 && Function.prototype.length=0
if (isFunction(obj) || isWindow(obj)) return false;
// type === "array" 数组
// length === 0 空的类数组
// 最后一个条件判断的是非空的类数组「有length属性,并且最大索引在对象中」
return type === "array" || length === 0 ||
typeof length === "number" && length > 0 && (length - 1) in obj;
};
// 检测是否为纯粹的对象 例如:{}
var isPlainObject = function isPlainObject(obj) {
var proto, Ctor;
// 不存在或者基于toString检测结果都不是[object Object],那么一定不是纯粹的对象
if (!obj || toString.call(obj) !== "[object Object]") {
return false;
}
// 获取当前值的原型链「直属类的原型链」
proto = getProto(obj);
// Object.create(null):这样创造的对象没有__proto__
if (!proto) return true;
// Ctor存储原型对象上的constructor属性,没有这个属性就是false
Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
// 条件成立说明原型上的构造函数是Object:obj就是Object的一个实例,并且obj.__proto__===Object.prototype
return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
};
// 检测是否为空对象
var isEmptyObject = function isEmptyObject(obj) {
// 排除非对象
if (obj == null) return false;
if (typeof obj !== "object") return false;
// 是一个对象「纯粹对象或者特殊对象都可以」
var keys = Object.keys(obj);
if (hasOwn.call(Object, 'getOwnPropertySymbols')) {
// 兼容这个属性的情况下,我们再去拼接
keys = keys.concat(Object.getOwnPropertySymbols(obj));
}
return keys.length === 0;
};
// 检测是否为数字
var isNumeric = function isNumeric(obj) {
var type = toType(obj);
return (type === "number" || type === "string") && !isNaN(+obj);
};
// 暴露到外部
var utils = {
toType: toType,
isFunction: isFunction,
isWindow: isWindow,
isArrayLike: isArrayLike,
isPlainObject: isPlainObject,
isEmptyObject: isEmptyObject,
isNumeric: isNumeric
};
if (typeof window !== "undefined") {
window._ = window.utils = utils;
}
if (typeof module === "object" && typeof module.exports === "object") {
module.exports = utils;
}
})();
复制代码
JS多种继承方式
/*
* JS本身是基于面向对象开发的编程语言
* =>类:封装、继承、多态
*
* 封装:类也是一个函数,把实现一个功能的代码进行封装,以此实现“低耦合高内聚”
* 多态:重载、重写
* 重写:子类重写父类上的方法(伴随继承运行的)
* 重载:相同的方法,由于参数或者返回值不同,具备了不同的功能(JS中不具备严格意义上的重载, JS中的重载:同一个方法内,根据传参不同实现不同的功能)
* 继承:子类继承父类中的方法
*/
/*
public void fn(int x, int y){
}
public void fn(int x){
}
fn(10, 20); 执行第一个fn
fn(10); 执行第二个fn
fn('string') 报错
*/
/*
function fn(x, y){
}
function fn(x){
}
fn(10, 20); 执行第一个fn
fn(10); 执行第二个fn
*/
/*
function fn(x, y){
if(y === undefined){
// ...
return
}
// ...
}
fn(10, 20);
fn(10);
*/
复制代码
/*
* 在JS语言中,它的继承和其他编程语言还是不太一样的
* 继承的目的:让子类的实例同时也具备父类中私有的属性和公共的方法
*/
function Parent() {
this.x = 100
}
Parent.prototype.getX = function getX () {
return this.x
}
function Child () {
this.y = 200
}
Child.prototype.getY = function getY () {
return this.y
}
let c1 = new Child
console.log(c1)
// JS中第一种继承方案:原型继承(让子类的原型等于父类的实例即可)
function Parent() {
this.x = 100
}
Parent.prototype.getX = function getX () {
return this.x
}
function Child () {
this.y = 200
}
Child.prototype = new Parent() // =>原型继承
Child.prototype.getY = function getY () {
return this.y
}
let c1 = new Child
console.log(c1)
复制代码
// JS中第二种继承方案:call继承(只能继承父类中私有的,不能继承父类中公共的)
function Parent() {
this.x = 100
}
Parent.prototype.getX = function getX () {
return this.x
}
function Child () {
// 在子类构造函数中,把父类当做普通函数执行(没有父类实例,父类原型上的那些东西也就和它没关系了)
// this -> Child的实例c1
Parent.call(this) // this.x = 100 相当于强制给c1这个实例设置一个私有的属性x,相当于让子类的实例继承了父类的私有属性,并且也变为了子类私有的属性“拷贝式”
this.y = 200
}
Child.prototype.getY = function getY () {
return this.y
}
let c1 = new Child
console.log(c1)
复制代码
// JS中第三种继承方案:寄生组合继承(call继承 + 另类原型继承)
function Parent() {
this.x = 100
}
Parent.prototype.getX = function getX () {
return this.x
}
function Child () {
Parent.call(this)
this.y = 200
}
// Child.prototype = deepClone(Parent.prototype)
// Child.prototype.__proto__ = Parent.prototype
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
Child.prototype.getY = function getY () {
return this.y
}
let c1 = new Child
console.log(c1)
复制代码
// ES6中类和继承
class Parent {
constructor () {
this.x = 100
}
// Parent.prototype.getX = function () {}
getX() {
return this.x
}
}
// 继承:extends Parent(类似寄生组合继承)
// 注意:继承后一定要在constructor第一行加上super()
class Child extends Parent {
constructor () {
super() //=>类似call继承 super(100,200):相当于把Parent中的constructor执行,传递了100和200
this.y = 200
}
getY() {
return this.y
}
}
// es6中创建的类,不能当作普通函数执行,只能new执行
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END