一、数据类型检测
1.typeof
/*
* typeof:用来检测数据类型的运算符
* typeof [value]
* @return
* 首先是个字符串
* 字符串中包含对应的数据类型,例如:"number"、"object"、"undefined"、"function"、"boolean"、"symbol"...
* @局限性
* typeof null =>"object"
* 不能具体区分对象数据类型的值(无法检测是正则还是数组等)
* typeof [] =>"object"
* typeof {} =>"object"
* typeof /^$/ =>"object"
* @优势
* 使用方便,所以在真实项目中,我们也会大量应用它来检测,尤其是在检测基本类型值(除null之外)和函数类型值的时候,它还是很方便的
*/
/* function func(n, m, callback) {
/!* 形参赋值默认值 *!/
//=>ES6: func(n = 0, m = 0)
//=>检测形参的值是否为UNDEFINED
// n === undefined ? n = 0 : null;
// typeof m === "undefined" ? m = 0 : null;
//=>基于逻辑或和逻辑与处理(瑕疵:不仅仅是不传赋值默认值,如果传递的值是假也会处理成为默认值)
// n = n || 0;
// m = m || 0;
/!* 回调函数执行 *!/
// typeof callback === "function" ? callback() : null;
// callback && callback(); //=>瑕疵:传递的为真即可执行,不一定是一个函数,这样写是开发者心里已经知道,要不然不传,要传就是一个函数
}
func(10, 20, function anonymous() {}); */
2.instanceof & constructor
/*
* instanceof:本意是用来检测实例是否隶属于某个类的运算符,我们基于这样的方式,也可以用来做某些数据类型的检测,例如:数组、正则等
* @局限性
* 不能处理基本数据类型值
* 只要在当前实例的原型链(__proto__)中出现过的类,检测结果都是true(用户可能会手动修改原型链的指向:example.__proto__ 或者 在类的继承中 等情况)
*/
// function func() {
// // arguments:类数组
// // arguments.__proto__ = Array.prototype;
// // console.log(arguments instanceof Array); //=>true
// }
// func();
// let arr = [],
// reg = /^$/,
// obj = {};
// console.log(arr instanceof Array); //=>true
// console.log(reg instanceof Array); //=>false
// console.log(arr instanceof Object); //=>true
// console.log(obj instanceof Array); //=>false
// console.log(1 instanceof Number); //=>false
//=>创建值的两种方式(不管哪种方式都是所属类的实例)
//字面量:let n = 12;
//构造函数:let m = new Number('12');
/*
* constructor:构造函数
* @原理:在类的原型上一般都会带有CONSTRUCTOR属性,存储当前类本身,我们也是利用这一点,获取某的实例CONSTRUCTOR属性值,验证是否为所属的类,从而进行数据类型检测
* @局限性:CONSTRUCTOR属性值太容易被修改了
*/
// let n = 12,
// arr = [];
// console.log(n.constructor === Number); //=>true
// console.log(arr.constructor === Array); //=>true
// console.log(arr.constructor === Object); //=>false
// arr.constructor = 111; //=>设置私有属性
// console.log(arr.constructor === Array); //=>false
// Func.prototype={}; //=>这样原型上没有CONSTRUCTOR属性(重构了)
3.Object.prototype.toString.call([value])
/*
* Object.prototype.toString.call([value]):调用Object原型上的toString方法,让方法执行的时候,方法中的this是要检测的数据类型 ,从而获取到数据类型所属类的详细信息
* @信息的模板
* "[object 所属类]" ,例如:"[object Array]"...
*
* 在所有的数据类型类中,他们的原型上都有toString方法,除Object.prototype.toString不是把数据值转换为字符串,其余的都是转为字符串,而Object原型上的toString是检测当前实例隶属类的详细信息的(检测数据类型)...
* obj.toString()
* 1.首先基于原型链查找机制,找到Object.prototype.toString
* 2.把找到的方法执行,方法中的this -> obj
* 3.方法内部把this(obj)的所属类信息输出
* =>方法执行,方法中的this是谁,就是检测谁的所属类信息
*
* 这个方法很强大,所有数据类型隶属的类信息检测的一清二楚
* "[object Number]"
* String/Boolean/Null/Undefined/Symbol/Object/Array/RegExp/Date/Math/Function...
*/
// let _obj = {},
// toString = _obj.toString;
// console.log(_obj.toString.call(100)); //=>"[object Number]"
// console.log(Object.prototype.toString.call(100)); //=>"[object Number]"
/* function func(n, m) {
return n + m;
}
let obj1 = {},
obj2 = {
name: '珠峰培训'
}; */
// console.log([12, 23].toString()); //=>"12,23"
// console.log(/^\d+$/.toString()); //=>"/^\d+$/"
// console.log(func.toString()); //=>"function func(n, m) {..."
// console.log(obj1.toString()); //=>"[object Object]"
// console.log(obj2.toString()); //=>"[object Object]"
4.封装一个数据类型检测的方法
【注】:真实项目可用
var _obj = {
isNumeric: "Number",
isBoolean: 'Boolean',
isString: 'String',
isNull: 'Null',
isUndefined: 'Undefined',
isSymbol: 'Symbol',
isPlainObject: 'Object',
isArray: 'Array',
isRegExp: 'RegExp',
isDate: 'Date',
isFunction: "Function",
isWindow: 'Window'
},
_toString = _obj.toString,
_type = {};
for (var key in _obj) {
if (!_obj.hasOwnProperty(key)) break;
_type[key] = (function () {
var reg = new RegExp("^\\[object " + _obj[key] + "\\]$");
return function anonymous(val) {
return reg.test(_toString.call(val));
}
})();
}
// console.log(_type.isNumeric(12));//true
二、回调函数
1.回调函数的基础概念
/*
* 把一个函数当做实参传递给另外一个函数,在另外一个函数执行的过程中,把传递进来的函数执行,这种机制就是回调函数
* @真实场景应用
* AJAX异步请求成功做什么事
* 浏览器内置的一些方法支持回调函数
* 插件组件封装中的钩子函数(生命周期函数)
* ......
*/
/* new Drag('.box', {
dragstart: function () {},
dragmove: function () {},
dragend: function () {}
}); */
/* let arr = [10, 20, 30];
//=>forEach/sort/map/find/filter/some/every/reduce...
arr.forEach((item, index) => {});
setTimeout(() => {}, 1000); */
/* function queryData(callback) {
$.ajax({
url: 'xxx.json',
method: 'get',
async: true,
success: result => {
typeof callback === 'function' ? callback(result) : null;
}
});
}
queryData(function anonymous(data) {
//=>data:服务器返回的结果
}); */
2.封装each方法【真实项目可用】
<script>
var _obj = {
isNumeric: "Number",
isBoolean: 'Boolean',
isString: 'String',
isNull: 'Null',
isUndefined: 'Undefined',
isSymbol: 'Symbol',
isPlainObject: 'Object',
isArray: 'Array',
isRegExp: 'RegExp',
isDate: 'Date',
isFunction: "Function",
isWindow: 'Window'
},
_toString = _obj.toString, //_toString相当于Object.prototype.toString
_type = {};
for (var key in _obj) {
if (!_obj.hasOwnProperty(key)) break;
_type[key] = (function () {
var reg = new RegExp("^\\[object " + _obj[key] + "\\]$");
return function anonymous(val) {
return reg.test(_toString.call(val));
}
})();
}
/*
* _each:遍历数组、类数组、对象中的每一项
* @params
* obj:需要迭代的数组、类数组、普通对象
* callback:回调函数(每遍历数组中的某一项,就会把回调函数执行一次;而且需要把当前遍历的内容和索引[属性值和属性名]传给回调函数;接收回调函数的返回结果,如果是false,则结束当前的循环,如果是其它值,让返回的值替换数组中的当前项,如果没有返回值,则什么都不处理...)
* context:传递的第三个参数,可以改变回调函数中的THIS指向,不传递默认是window
* @return
* 返回一个新的数组或者对象(原来的数组或者对象不变)
*/
function _each(obj, callback, context = window) {
let isLikeArray = _type.isArray(obj) || (('length' in obj) && _type.isNumeric(obj.length));
typeof callback !== "function" ? callback = Function.prototype : null; //Function.prototype是一个匿名空函数
//=>数组或者类数组
if (isLikeArray) {
let arr = [...obj];
for (let i = 0; i < arr.length; i++) {
let item = arr[i],
result = callback.call(context, item, i);
if (result === false) break;
if (typeof result === "undefined") continue;
arr[i] = result;
}
return arr;
}
//=>对象的处理
let opp = {
...obj
};
for (let key in opp) {
if (!opp.hasOwnProperty(key)) break;
let value = opp[key],
result = callback.call(context, value, key);
if (result === false) break;
if (typeof result === "undefined") continue;
opp[key] = result;
}
return opp;
}
//测试数组
let arr = [10, 20, 30, 40];
let arr2 = _each(arr, (item, index) => {
console.log(item, index);//10 0; 20 1; 30 2
if (index >= 2) return false;
return item * 10;
});
console.log(arr, arr2);// [10, 20, 30, 40] [100, 200, 30, 40]
//测试类数组
/* function func() {
let arr = _each(arguments, (item, index) => {
console.log(item, index);//10 0; 20 1; 30 2
if (index >= 2) return false;
return item * 10;
});
console.log(arguments, arr);
// Arguments(4)[10, 20, 30, 40] [100, 200, 30, 40]
}
func(10, 20, 30, 40); */
//测试对象
/*let obj = {
name: '珠峰',
year: 10,
teacher: '哇咔咔~'
};
let obj2 = _each(obj, function (value, key) {
// console.log(this); //=>document
// console.log(value, key);
if (key === "name") {
return "珠峰培训@zhufeng";
}
}, document);
console.log(obj, obj2);
//{name: "珠峰", year: 10, teacher: "哇咔咔~"}
//{name: "珠峰培训@zhufeng", year: 10, teacher: "哇咔咔~"} */
</script>
3.字符串replace方法的封装
/*
* 重写字符串内置方法replace
* 1.正则在字符串中匹配几次,我们传递的回调函数就会被执行几次(前提:正则设置了global修饰符)
* 2.每一次执行回调函数,都把当前正则匹配的信息(既有大正则,也有小分组的)传递给回调函数
* 3.还要接收回调函数的返回值,返回的是啥内容,就要把当前字符串中正则匹配这一部分内容替换成啥
*/
/* ~ function () {
//=>处理字符串:把字符串中的某一项替换成另外一项
function handle(str, val1, val2) {
let index = str.indexOf(val1);
return str.substring(0, index) + val2 + str.substring(index + val1.length);
}
function replace(reg, callback) {
let _this = this.substring(0),
isGlobal = reg.global,
arr = reg.exec(this);
while (arr) {
//=>捕获到的结果是数组(执行回调函数,把捕获的结果传递给它);还要接收回调函数执行的返回值,用返回值替换字符串中当前正则匹配的内容;
if (typeof callback === "function") {
let res = callback.apply(null, arr);
_this = handle(_this, arr[0], res);
}
arr = reg.exec(this);
//=>不设置GLOBAL的情况执行一次
if (!isGlobal) break;
}
return _this;
}
String.prototype.replace = replace;
}();
let str = "{0}年{1}月{2}日",
arr = ['2019', '09', '03'];
str = str.replace(/\{(\d)\}/g, function (content, group1) {
return '@#' + arr[group1];
});
console.log(str); */
三、发布订阅
1.发布订阅和DOM2事件池机制
DOM0和DOM2
1.语法上的区别
box.onclick=function(){}
box.addEventListener('click',function(){})
2.底层运行机制上的区别
DOM0就是给元素的某个属性绑定方法(有效绑定的方法只有一个)
DOM2是基于事件池机制完成,每增加一个绑定的方法,都会往事件池中存放一个...当事件触发会依次执行事件池中的事情 =>【发布订阅其实就是模拟的事件池机制(可以给同一个元素的某个事件绑定多个不同的方法)】
3.DOM2中可以给一些特殊的事件类型绑定方法,这些事件类型DOM0不支持绑定,例如:
DOMContentLoaded、transitionend. . .I
$(document).ready() =>$(function(){}) VS window.onload
DOM2的事件池机制
1.基于addEventListener/attachEvent(IE6~8)向事件池中追加方法:新版本浏览器会根据元素和事件类型对新增的方法做重复校验,但是IE6~8不可以
2.当事件行为触发,会把事件池中的方法按照增加的顺序依次执行,但是IE6~8中执行的顺序是不固定的
2.JQuery中的发布订阅
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button class="sumbit">点我啊</button>
<script src="js/jquery.min.js"></script>
<script>
// 创建一个事件池 $.callbacks()
let $pond1 = $.Callbacks();
$('.sumbit').click(function () {
// 点击的时候通知事件池中的方法执行,而且还可以给每个方法都传递实参
$pond1.fire(100, 200);
});
let fn1 = function () {
console.log(1);
};
let fn2 = function () {
console.log(2);
};
let fn3 = function () {
console.log(3);
};
// 把需要做的事情陆续添加到事件池中 $pond.add(func)/$pond.remove(func)
$pond1.add(fn1);
// $pond1.add(fn1);//=>JQ中没有做去重处理
$pond1.add(fn2);
$pond1.add(fn3);
let fn4 = function (n, m) {
console.log(4, n + m);//4 300
};
$pond1.add(fn4);
</script>
</body>
</html>
3.基于ES6封装发布订阅库
【subscribe.js】:自己封装的发布订阅库
let _subscribe = (function () {
// Subscribe:发布订阅类
class Subscribe {
constructor() {
// 创建一个事件池,用来存储后期需要执行的方法
this.$pond = [];
}
// 向事件池中追加方法(重复处理)
add(func) {
let flag = this.$pond.some(item => {
return item === func;
});
!flag ? this.$pond.push(func) : null;
}
// 从事件池中移除方法
remove(func) {
let $pond = this.$pond;
for (let i = 0; i < $pond.length; i++) {
let item = $pond[i];
if (item === func) {
// 移除(顺序不变的情况下基本只能用splice)
$pond.splice(i, 1);
break;
}
}
}
// 通知事件池中的方法按照顺序依次执行
fire(...args) {
let $pond = this.$pond;
for (let i = 0; i < $pond.length; i++) {
let item = $pond[i];
item.call(this, ...args);
}
}
}
// 暴露给外面使用
return function subscribe() {
return new Subscribe();
}
})();
导入subscribe.js库,开始使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button class="sumbit">点我啊</button>
<script src="js/subscribe.js"></script>
<script>
let pond=_subscribe();
document.querySelector('.sumbit').onclick=function(ev){
pond.fire(ev);
}
let fn1 = function () {
console.log(1);
};
let fn2 = function () {
console.log(2);
};
pond.add(fn1);
pond.add(fn1);//自己的方法实现去重了
pond.add(fn2);
let fn3 = function () {
console.log(3);
};
let fn4 = function (ev) {
console.log(4, ev);//4 MouseEvent{...}
};
pond.add(fn3);
pond.add(fn4);
</script>
</body>
</html>
4.解决数组塌陷问题
【subscribe.js】:自己封装的发布订阅库----最终版
let _subscribe = (function () {
// Subscribe:发布订阅类
class Subscribe {
constructor() {
// 创建一个事件池,用来存储后期需要执行的方法
this.$pond = [];
}
// 向事件池中追加方法(重复处理)
add(func) {
let flag = this.$pond.some(item => {
return item === func;
});
!flag ? this.$pond.push(func) : null;
}
// 从事件池中移除方法
remove(func) {
let $pond = this.$pond;
for (let i = 0; i < $pond.length; i++) {
let item = $pond[i];
if (item === func) {
// 移除(顺序不变的情况下基本只能用splice),但是不能这样写,这样会导致数组塌陷问题,我们不能真移除,只能把当前项赋值为null
// $pond.splice(i, 1);
$pond[i] = null;
break;
}
}
}
// 通知事件池中的方法按照顺序依次执行
fire(...args) {
let $pond = this.$pond;
for (let i = 0; i < $pond.length; i++) {
let item = $pond[i];
if (typeof item !== 'function') {
// 此时再删除
this.$pond.splice(i, 1);
i--;
continue;
}
item.call(this, ...args);
}
}
}
// 暴露给外面使用
return function subscribe() {
return new Subscribe();
}
})();
导入subscribe.js库,开始使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button class="sumbit">点我啊</button>
<script src="js/subscribe.js"></script>
<!-- <script>
let pond=_subscribe();
document.querySelector('.sumbit').οnclick=function(ev){
pond.fire(ev);
}
let fn1 = function () {
console.log(1);
};
let fn2 = function () {
console.log(2);
};
pond.add(fn1);
pond.add(fn1);//自己的方法实现去重了
pond.add(fn2);
let fn3 = function () {
console.log(3);
};
let fn4 = function (ev) {
console.log(4, ev);//4 MouseEvent{...}
};
pond.add(fn3);
pond.add(fn4);
</script> -->
<script>
let pond=_subscribe();
document.querySelector('.sumbit').onclick=function(ev){
pond.fire(ev);
}
let fn1 = function () {
console.log(1);
};
let fn2 = function () {
console.log(2);
pond.remove(fn1); //需要注意的地方
};
let fn3 = function () {
console.log(3);
};
let fn4 = function () {
console.log(4);
};
pond.add(fn1);
pond.add(fn2);
pond.add(fn3);
pond.add(fn4);
</script>
</body>
</html>
四、继承【4种】
1.函数封装重载和对面向对象的理解
/*
* 封装:低耦合高内聚
* 多态:重载和重写
* 【重载】:方法名相同,形参个数或者类型不一样(JS中不存在真正意义上的重载,JS中重载指的是同一个方法,根据传参不同,实现出不同的效果)
* 【重写】:在类的继承中,子类可以重写父类中的方法,
* 继承:子类继承父类中的属性和方法
*/
/*
function sum(x, y, z) {
if (typeof z === 'undefined') {
//...
return;
}
//...
}
sum(1, 2);
sum(1, 2, 3);
*/
/*
function sum(n, m) {
console.log(1);
}
function sum(n, m, x) {
console.log(2);
}
sum(1, 2); //2
sum(1, 2, 3); //2
*/
/*
JAVA中是存在重载的:根据传递参数的个数和类型,执行对应的函数
public void sum(int n,int m){}
public void sum(int n,int m,float x){}
public void sum(int n,int m,String x){}
sum(1,2);
sum(1,2,3.5);
sum(1,2,'DD');
sum();//报错,找不到
*/
2.原型继承
/*
* 继承:子类继承父类中的属性和方法(目的:让子类的实例可以调取父类中的属性和方法)
* 方案一:原型继承
* 让父类中的属性和方法在子类实例的原型链上
* child.prototype = new Parent();
* child.prototype.constructor = child;
* 特点:
* 1.不像其他语言中的继承一样(其他语言的继承一般是拷贝继承,也就是子类继承父类,会把父类中的属性和方法拷贝一份到子类中,供子类的实例调取使用),他是把父类的原型放到子类实例的原型上,实例像调取这些方法,是基于__proto__原型链查找机制完成的
* 2.子类可以重写父类上的方法(这样会导致父类的其他实例也收到影响)
* 3.父类中私有或者公有的属性方法,最后都会变为子类中公有的属性和方法
*/
function A(x) {
this.x = x;
}
A.prototype.getX = function () {
console.log(this.x);
}
B.prototype = new A(200);
function B(y) {
this.y = y;
}
B.prototype.getY = function () {
console.log(this.y);
}
let b1 = new B(100);
console.log(b1.y);// 100
console.log(b1.x);// 200
b1.getY();// 100
b1.getX();// 200
3.call继承
/*
* 继承:子类继承父类中的属性和方法(目的:让子类的实例可以调取父类中的属性和方法)
* 方案二:call继承
* 特点:child方法中把parent当做普通函数执行,让parent中的this指向child的实例,相当于给child的实例设置了很多私有的属性或者方法
* 1.只能继承父类私有的属性或者方法(因为是把parent当做普通函数执行,和其原型上的属性和方法没有关系)
* 2.父类私有的变为子类私有的
*/
function A(x) {
this.x = x;
}
A.prototype.getX = function () {
console.log(this.x);
}
function B(y) {
// this: B的实例b1
A.call(this,200);// b1.x = 200
this.y = y;
}
B.prototype.getY = function () {
console.log(this.y);
}
let b1 = new B(100);
console.log(b1.y); // 100
console.log(b1.x); // 200
b1.getY(); // 100
4.寄生组合继承【推荐】
<script>
/*
* 继承:子类继承父类中的属性和方法(目的:让子类的实例可以调取父类中的属性和方法)
* 方案三:寄生组合继承 = call继承 + 类似于原型继承
* 特点:父类私有和公有分别是子类实例的私有和公有属性方法(推荐)
*/
function A(x) {
this.x = x;
}
A.prototype.getX = function () {
console.log(this.x);
}
function B(y) {
// this: B的实例b1
A.call(this, 200); // b1.x = 200
this.y = y;
}
// Object.create(obj): 创建一个空对象,让空对象__proto__指向obj
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
B.prototype.getY = function () {
console.log(this.y);
}
let b1 = new B(100);
console.log(b1.y); // 100
console.log(b1.x); // 200
b1.getY(); // 100
b1.getX(); // 200
</script>
<script>
// 【补充】:重写内置Object.create
// Object.create=function(obj){
// function Fn(){}
// Fn.prototype = obj;
// return new Fn();
// }
</script>
5.ES6中的继承
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
/*
class A {
constructor(x) {
this.x = x;
}
getX() {
console.log(this.x);
}
}
class B {
constructor(y) {
// A.call(this, 200);//Uncaught TypeError: Class constructor A cannot be invoked without 'new'
this.y = y;
}
getY() {
console.log(this.y);
}
}
// B.prototype = Object.create(A.prototype);//不允许重定向原型的指向
let b1 = new B(200);
*/
</script>
<script>
class A {
constructor(x) {
this.x = x;
}
getX() {
console.log(this.x);
}
}
// ES6中的继承: class child extends parent{} => B.prototype.__proto__ = A.prototype
class B extends A {
constructor(y) {
//子类只要继承父类,可以不写constructor,一旦写了,则在constructor中的第一句话必须始super();
//不写constructor,浏览器会自己默认创建constructor(...args){super(...args)}
super(200); //相当于A.call(this,200); => 把父类当做普通方法执行,给方法传递参数,让方法中的this是子类的实例
this.y = y;
}
getY() {
console.log(this.y);
}
}
let b1 = new B(100);
console.log(b1); //B {x: 200, y: 100}
</script>
<script>
/*
// ES6中的继承实战应用1:
class Login extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
//此处的this:Login的实例
this.setState(); // React.Component.prototype.setState
}
render() {
}
componentDidMount() {
}
}
*/
/*
// ES6中的继承实战应用2:
// Promise.prototype上的方法:then、catch、finally
class Dialog extends Promise {
constructor() {
super();
}
show() {
//此处的this:Dialog的实例
this.then().then(); //真实项目一般不这么写
}
}
*/
/*
// ES6中的继承实战应用:
class Utils {
query() {}
}
class Dialog extends Utils {
constructor() {
super();
this.query(); //this: Dialog的实例,通过继承,实例可以调取父类原型上的方法
}
}
*/
</script>
</body>
</html>