instanceof函数
原理
instanceof 判断基本数据类型的方法
instanceof运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
其实就是自定义
instanceof
行为的一种方式,这里将原有的instanceof
方法重定义,换成了typeof
,因此能够判断基本数据类型。
instanceof 语法
function Foo () {
}
let f = new Foo();
console.log(f instanceof Foo); // true
实列
[] instanceof Array; // true
[] instanceof Object; // true
Array instanceof Object; // true
Object instanceof Array; // false
Object instanceof Object; // true
Number instanceof Number; // false
Array instanceof Array; // false
Function instanceof Object; // true
Object instanceof Function; // true
Object instanceof Object; // true
Function instanceof Function; // true
String instanceof Function; // true
Array instanceof Function; // true
Number instanceof Function; // true
"" instanceof String; // false
1 instanceof Number; // false
false instanceof Boolean; // false
错误使用的报错
Array instanceof [];
// Uncaught TypeError: Right-hand side of 'instanceof' is not callable
{} instanceof Object;
// VM91:1 Uncaught SyntaxError: Unexpected token 'instanceof'
判断Object
的prototype是否在a
的原型链上。
#实现
function myInstanceof(target, origin) {
const proto = target.__proto__;
if (proto) {
if (origin.prototype === proto) {
return true;
} else {
return myInstanceof(proto, origin)
}
} else {
return false;
}
}
ES5继承
每个函数都会创建一个prototype
属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处是,在它上面定义的属性和方法都可以被对象实例共享。原来在构造函数中直接赋给对象实例的值,可以直接赋值给它们的原型。
有下面两个类,下面实现Man
继承People
:
function People() {
this.type = 'prople'
}
People.prototype.eat = function () {
console.log('吃东西啦');
}
function Man(name) {
this.name = name;
this.color = 'black';
}
#原型继承
将父类指向子类的原型。
Man.prototype = new People();
缺点:原型是所有子类实例共享的,改变一个其他也会改变。
#构造继承
在子类构造函数中调用父类构造函数
function Man(name) {
People.call(this);
}
缺点:不能继承父类原型,函数在构造函数中,每个子类实例不能共享函数,浪费内存。
#组合继承
使用构造继承继承父类参数,使用原型继承继承父类函数
function Man(name) {
People.call(this);
}
Man.prototype = People.prototype;
缺点:父类原型和子类原型是同一个对象,无法区分子类真正是由谁构造。
#寄生组合继承
在组合继承的基础上,子类继承一个由父类原型生成的空对象。
function Man(name) {
People.call(this);
}
Man.prototype = Object.create(People.prototype, {
constructor: {
value: Man
}
})
#inherits函数:
方法的内部会进行严格的参数合法性检查,参数必须是两个,即一个子类,一个父类;另外还要求两个参数都必须是function类型,否则抛出异常,宣告继承失败
原理
instanceof 判断基本数据类型的方法
其实就是自定义
instanceof
行为的一种方式,这里将原有的instanceof
方法重定义,换成了typeof
,因此能够判断基本数据类型。
a instanceof Object
判断Object
的prototype是否在a
的原型链上。
#实现
function myInstanceof(target, origin) {
const proto = target.__proto__;
if (proto) {
if (origin.prototype === proto) {
return true;
} else {
return myInstanceof(proto, origin)
}
} else {
return false;
}
}
ES5继承
每个函数都会创建一个prototype
属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处是,在它上面定义的属性和方法都可以被对象实例共享。原来在构造函数中直接赋给对象实例的值,可以直接赋值给它们的原型。
有下面两个类,下面实现Man
继承People
:
function People() {
this.type = 'prople'
}
People.prototype.eat = function () {
console.log('吃东西啦');
}
function Man(name) {
this.name = name;
this.color = 'black';
}
#原型继承
将父类指向子类的原型。
Man.prototype = new People();
缺点:原型是所有子类实例共享的,改变一个其他也会改变。
#构造继承
在子类构造函数中调用父类构造函数
function Man(name) {
People.call(this);
}
缺点:不能继承父类原型,函数在构造函数中,每个子类实例不能共享函数,浪费内存。
#组合继承
使用构造继承继承父类参数,使用原型继承继承父类函数
function Man(name) {
People.call(this);
}
Man.prototype = People.prototype;
缺点:父类原型和子类原型是同一个对象,无法区分子类真正是由谁构造。
#寄生组合继承
在组合继承的基础上,子类继承一个由父类原型生成的空对象。
function Man(name) {
People.call(this);
}
Man.prototype = Object.create(People.prototype, {
constructor: {
value: Man
}
})
#inherits函数:
方法的内部会进行严格的参数合法性检查,参数必须是两个,即一个子类,一个父类;另外还要求两个参数都必须是function类型,否则抛出异常,宣告继承失败
每个函数就是一个对象(Function),函数对象都有一个子对象 prototype对象,类是以函数的形式来定义的。prototype表示该函数的原型,也表示一个类的成员的集合。
function inherits(ctor, superCtor) {
ctor.super_ = superCtor;
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
};
使用:
function Man() {
People.call(this);
//...
}
inherits(Man, People);
Man.prototype.fun = ...
function inherits(ctor, superCtor) {
ctor.super_ = superCtor;
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
};
使用:
function Man() {
People.call(this);
//...
}
inherits(Man, People);
Man.prototype.fun = ...
promise封装一个ajax
步骤
- 首先,通过
new XMLHttpRequest
创建一个xhr对象。 - 根据get方法和post方法进行数据请求,在使用这个xhr对象时,要调用的第一个方法是
open()
xhr.open("get",url,false)
调用open并不会真正的发送请求,只是启动一个请求以备发送。
- 发送请求时调用
send()方法
xhr.send(data);//对应post方法
xhr.send(null);//对应get方法
send方法接受一个参数,作为请求主体发送的数据。如果不需要通过请求主体发送数据最好传入null。
- 如果仅仅是同步请求,接下来就可以根据xhr.status属性来判断数据请求情况。status就是响应的HTTP状态码。注意304这一特殊状态码。
function ajax(url, method = 'get', param = {}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const paramString = getStringParam(param);
if (method === 'get' && paramString) {
url.indexOf('?') > -1 ? url += paramString : url += `?${paramString}`
}
xhr.open(method, url);
xhr.onload = function () {
const result = {
status: xhr.status,
statusText: xhr.statusText,
headers: xhr.getAllResponseHeaders(),
data: xhr.response || xhr.responseText
}
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
resolve(result);
} else {
reject(result);
}
}
// 设置请求头
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// 跨域携带cookie
xhr.withCredentials = true;
// 错误处理
xhr.onerror = function () {
reject(new TypeError('请求出错'));
}
xhr.timeout = function () {
reject(new TypeError('请求超时'));
}
xhr.onabort = function () {
reject(new TypeError('请求被终止'));
}
if (method === 'post') {
xhr.send(paramString);
} else {
xhr.send();
}
})
}
function getStringParam(param) {
let dataString = '';
for (const key in param) {
dataString += `${key}=${param[key]}&`
}
return dataString;
}
单列模式
在合适的时候才创建对像,并且只创建唯一的一个。
创建对象和管理单例的职责被分布在两个不同的方法中,这两个方法组合起来才具有单例模式的威力。
使用闭包实现:
var Singleton = function(name) {
this.name = name;
};
Singleton.prototype.getName = function() {
alert(this.name);
};
Singleton.getInstance = (function(name) {
var instance;
return function(name){
if (!instance) {
instance = new Singleton(name);
}
return instance;
}
})();
var a = Singleton.getInstance('ConardLi');
var b = Singleton.getInstance('ConardLi2');
console.log(a===b); //true
异步循环打印
如果程序就这样一步步的执行下去,条例清晰,但是当我们遇到阻塞时,比如一个耗时的Ajax请求,或者读写文件之类的操作,由于js是单线程的,其他后续的操作也都将被阻塞,我们就需要异步的进行一些操作
var response = fetch('myImage.png');
var blob = response.blob();
// display your image blob in the UI somehow
对于上面的代码来说,fetch提供了异步读取网络文件的能力,相当于将fetch行为加入了异步队列中,只能等待同步的代码执行完成之后才会开始清理异步队列的任务。这就是一个异步编程,但是这是有问题的,因为fetch行为是处于异步队列的,后续的代码不能确定什么时候获取到response,此时引入callback(回调函数),或者promise
异步callbacks
异步callbacks其实就是函数,只不过作为参数传递给那些异步执行的其他函数,当异步执行的函数
使用promise + async await
实现异步循环打印
var sleep = function (time, i) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(i);
}, time);
})
};
var start = async function () {
for (let i = 0; i < 6; i++) {
let result = await sleep(1000, i);
console.log(result);
}
};
start();
图片懒加载
监听图片高度
图片,用一个其他属性存储真正的图片地址:
<img src="loading.gif" data-src="https://cdn.pixabay.com/photo/2015/09/09/16/05/forest-931706_1280.jpg" alt="">
<img src="loading.gif" data-src="https://cdn.pixabay.com/photo/2014/08/01/00/08/pier-407252_1280.jpg" alt="">
<img src="loading.gif" data-src="https://cdn.pixabay.com/photo/2014/12/15/17/16/pier-569314_1280.jpg" alt="">
<img src="loading.gif" data-src="https://cdn.pixabay.com/photo/2010/12/13/10/09/abstract-2384_1280.jpg" alt="">
<img src="loading.gif" data-src="https://cdn.pixabay.com/photo/2015/10/24/11/09/drop-of-water-1004250_1280.jpg"
通过图片offsetTop
和window
的innerHeight
,scrollTop
判断图片是否位于可视区域。
var img = document.getElementsByTagName("img");
var n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历
lazyload(); //页面载入完毕加载可是区域内的图片
// 节流函数,保证每200ms触发一次
function throttle(event, time) {
let timer = null;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
timer = null;
event.apply(this, args);
}, time);
}
}
}
window.addEventListener('scroll', throttle(lazyload, 200))
function lazyload() { //监听页面滚动事件
var seeHeight = window.innerHeight; //可见区域高度
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度
for (var i = n; i < img.length; i++) {
console.log(img[i].offsetTop, seeHeight, scrollTop);
if (img[i].offsetTop < seeHeight + scrollTop) {
if (img[i].getAttribute("src") == "loading.gif") {
img[i].src = img[i].getAttribute("data-src");
}
n = i + 1;
}
}
}
#IntersectionObserver
IntersectionObserver接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。祖先元素与视窗(viewport)被称为根(root)。
Intersection Observer
可以不用监听scroll
事件,做到元素一可见便调用回调,在回调里面我们来判断元素是否可见。
if (IntersectionObserver) {
let lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry, index) => {
let lazyImage = entry.target;
// 如果元素可见
if (entry.intersectionRatio > 0) {
if (lazyImage.getAttribute("src") == "loading.gif") {
lazyImage.src = lazyImage.getAttribute("data-src");
}
lazyImageObserver.unobserve(lazyImage)
}
})
})
for (let i = 0; i < img.length; i++) {
lazyImageObserver.observe(img[i]);
}
}