1、监听用户粘贴文本/读取剪贴板的值
// 创建一个输入框
let textInput = document.createElement('div');
textInput.innerHTML = '<label for="text"></label>\n' +
'<textarea id="text" cols="40" rows="10"></textarea>';
document.body.appendChild(textInput);
// 给文本输入框绑定粘贴事件,通过用户的行为获取事件
textInput.onpaste = function (ev) {
// 防止默认事件, 这里的默认事件是用户触发粘贴成功,并将值写入的过程
ev.preventDefault();
// 停止事件冒泡
ev.stopPropagation();
// 获取剪贴版的内容
// 'text/html' 获得的是剪贴板的html文档
// 'text/plain' 获得的是剪贴板的当前黏贴贴的纯文本文档
let context = ev.clipboardData.getData("text/plain");
console.log(context);
}
// 自动获取剪贴板的值
// 但是这个会弹出一个提示框,需要征得用户同意才可以获得
navigator.clipboard.readText().then(value => {
console.log(value);
}, reason => {
console.log(reason);
}
)
2、深拷贝(地址拷贝)和浅拷贝(拷贝指针)
let target = {name: 'alex', friends: ['tom', 'jhon']}
// 拷贝
let coped = Object.assign({}, target);
console.log(coped);
// 首先拷贝了一个对象
// {name: 'alex', friends: Array(2)}
// 第一层元素是深拷贝,拷贝了原来地址中的元素,然后在堆区新开辟的一个地址来储存这个元素
// 因此修改了第一层的元素相当于是在自己的地址上修改的,和被拷贝的元素的地址是不同的
// 所以修改了拷贝的元素的第一层属性的值,被拷贝元素的值是不变的
coped.name = 'jack';
console.log(coped);
// {name: 'jack', friends: Array(2)}
console.log(target);
// {name: 'alex', friends: Array(2)}
// 由于第二层拷贝的只是一个指针,但是指针指向的是相同的地址
// 所以通过这个指针修改元素,那么无论有多少个指针,只要指向的地址是相同的
// 那么访问的元素就是相同的,
// 只要是只是拷贝了指针的拷贝,就叫做浅拷贝
coped.friends[0] = 'modified';
console.log(coped);
console.log(target);
// friends: (2) ['modified', 'jhon']
// 只有一个等号的拷贝是浅拷贝,只是复制了指向地址的指针而已
let target_ = target;
target_.name = 'new-name'
console.log(target);
// {name: 'new-name', friends: Array(2)}
// 深拷贝,通过一下的方式实现
// JSON.stringify();将对象转化为字符串
// JSON.parse();将字符串转化为对象
let obj1 = JSON.stringify(target);
let obj2 = JSON.parse(obj1);
// 此时 obj1 与 obj2 在堆中指向的地址是不同的,所以是深拷贝
// 递归函数实现深拷贝
let check_type = data => {
// 判断数据类型,经常通过toString方法来看数据类型
return Object.prototype.toString.call(data).slice(8, -1)
}
let deepCopy = my_target => {
let type = check_type(my_target);
let result;
if (type === 'Object') {
result = {}
} else if (type === 'Array') {
result = []
} else {
// 基本数据类型不做处理,这里只考虑{} 和 []
return my_target
}
for (let i in my_target) {
let value = my_target[+i];
let inner_type = check_type(value);
if (inner_type === 'Object' || inner_type === 'Array') {
result[+i] = deepCopy(value);
} else {
result[+i] = value;
}
}
return result;
}
let arr1 = [1, 2, [1, 2, 3]];
let arr2 = deepCopy(arr1);
arr2[arr2.length - 1][0] = 'changed';
console.log(arr1); // [1, 2, [1, 2, 3]]
console.log(arr2); // [1, 2, ['changed', 2, 3]]
3、监视用户复制文本事件
let my_div = document.createElement('div');
my_div.innerHTML = 'this is a text';
document.body.appendChild(my_div);
my_div.oncopy = function (ev) {
// 阻止事件默认的行为
ev.preventDefault();
// 阻止事件的冒泡
ev.stopPropagation();
// 获取选中的内容的内容对象
const selection = document.getSelection();
// 从内容对象中获取文字选中的部分
const selected_data = selection.toString();
// 将文字选中部分的值转移到剪贴版上
ev.clipboardData.setData('text/plain', selected_data);
}
4、监听用户剪切文本事件
let my_div = document.createElement('div');
my_div.innerHTML = 'for cut event';
document.body.appendChild(my_div);
my_div.oncut = function (ev) {
// 阻止默认事件
ev.preventDefault();
// 阻止事件冒泡
ev.stopPropagation();
// 获取用户选中对象
const selection = document.getSelection();
// 从用户选中对象中获取用户选中文本
const text = selection.toString();
// 将文本复制到用户剪贴板
ev.clipboardData.setData('text/plain', text);
// 删除用户在文本中所选中的文本,更新文本
my_div.innerHTML = my_div.innerHTML.replace(text, '');
}
5、监听用户控制台是否打开
setInterval(function () {
check()
}, 4000);
var check = function () {
function doCheck(a) {
if (("" + a / a)["length"] !== 1 || a % 20 === 0) {
(function () {
}
["constructor"]("debugger")())
} else {
(function () {
}
["constructor"]("debugger")())
}
// a 表示的是点击调试按钮的次数
doCheck(++a)
}
try {
doCheck(0)
} catch (err) {
}
};
check();
// 虽然看不懂这个代码,但是只能猜测一下做了什么
// function () {}, 这个代码应该是获得一个函数对象
// ["constructor"] 这个应该是函数对象上的一个属性
// 例如 obj = {a:10} 可以通过 obj['a'] 的方法获取
// 拿到属性后将 debugger 传入构造函数, 最后 () 表示执行构造函数
// 说人话就是在 Function的原型链的构造函数上添加了debugger 属性
// 当用户打开控制台的时候,浏览器就会试着去加载内置的函数
// 内置的函数原型Function会默认执行构造函数constructor,于是就一直debugger下去
// 所以解决这种方案一般是更改原型对象Function的构造函数, 在浏览器控制台中输入以下代码即可
// Function.prototype.constructor = function (){};
6、原生XHR实现POST请求三种编码方式书写
// post 提交数据的方式的类型主要由四种组成
// 协议规定 POST 提交的数据必须放在消息主体(entity-body)中,但协议并没有规定数据必须使用什么编码方式
// 服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。
const url = 'https://www.example.com';
/*
第一种传输的是是form表单已经编码后的数据 -> application/x-www-form-urlencoded
一般的form表单使用的就是这种提交数据的方式
POST http://www.example.com HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8
title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3
*/
let xhr1 = new XMLHttpRequest();
xhr1.setRequestHeader('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
xhr1.open('POST', url);
// 下面传入的是编码后的data
xhr1.send('key1=val1&key2=val2');
/*
第二种传输方式类型为multipart/form-data,主要用来传输文本等内容
如果用form表单实现文件上传需要指定这一个类型
// 需要标签来辅助
// <input type="file" id="file1" />
POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data
*/
let input_type_of_file = document.querySelector('input[type=file]');
let xhr2 = new XMLHttpRequest();
// 这个请求头是默认的设置,在这里可以跳过,不用设置
xhr2.setRequestHeader('content-type', 'multipart/form-data');
xhr2.open('POST', url);
let data = new FormData();
data.append('upload_file_name', input_type_of_file.files);
data.set('key', 'value');
xhr2.send(data);
// 显示文件上传进度
xhr2.upload.onprogress = function (ev){
// ev.lengthComputable 是一个布尔值,表示当前上传的资源是否具有可计算的长度
if (ev.lengthComputable){
// ev.loaded 已传输的字节
// ev.total 需传输的总字节
let ratio = Math.ceil((ev.loaded / ev.total) * 100);
}
}
// 上面提到的这两种 POST 数据的方式,都是浏览器原生支持的,而且现阶段原生 form 表单也只支持这两种方式。
/*
第三种发送json数据类型
application/json
POST http://www.example.com HTTP/1.1
Content-Type: application/json;charset=utf-8
{"title":"test","sub":[1,2,3]}
*/
let xhr3 = new XMLHttpRequest();
xhr3.open('POST', url);
let json_data = {'key': 'value'}
xhr3.send(JSON.stringify(json_data));
/*
第四种发送xml文件格式, text/xml
POST http://www.example.com HTTP/1.1
Content-Type: text/xml
<?xml version="1.0"?>
<methodCall>
<methodName>examples.getStateName</methodName>
<params>
<param>
<value><i4>41</i4></value>
</param>
</params>
</methodCall>
*/
// 由于几乎用不到,这里就不写代码了
7、js简单实现cookie解析为对象
function parse_cookie(cookie_){
let cookie = cookie_ ? cookie_: document.cookie;
let cookie_array = cookie.split(';');
let cookie_obj = {};
for (let single_cookie of cookie_array){
let s = single_cookie.split('=');
cookie_obj[s[0].trim()] = s[1].trim();
}
return cookie_obj;
}
8、事件委托和事件冒泡
/*
事件委托(原理就是事件冒泡)
事件冒泡就是子元素存在的事件冒泡向父元素传递,使父元素也触发相同的事件
批量添加事件监听
题目:页面上有一个无数个<li>元素请批量给它们添加点击事件监听,
实现效果:点击哪个<1i>元素,哪个<1i>元素就变红
*/
let dom =`<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>`;
let wrapper = document.createElement('div');
wrapper.innerHTML = dom;
document.body.appendChild(wrapper);
// 给每一个li元素添加事件,点击让他们变红
let li = [...document.getElementsByTagName('li')];
li.forEach(function (item, index){
item.onclick = function (){
this.style.color = 'red';
}
})
/*
每一个事件监听注册都会消耗内存,监听数量太多,内存消耗会非常大
实际上,每个<1i>的事件处理函数都是不同的函数,这些函数本身也会占用内存
*/
/*
题目:页面上有一个无序列表,其中没有<1i>元素,请制作一个按钮,点击
这个按钮就能增加li,并且实现和上面一样的效果
*/
let button = document.createElement('button');
button.innerHTML = '点击我添加li';
document.body.appendChild(button);
button.onclick = function (){
let li_ = document.createElement('li');
let father = document.querySelector('ul');
li_.innerHTML = '我是按钮点击添加的';
// 如果这里还要实现点击变红的效果,就要分别再次绑定事件。
// 也就是也会消耗大量的内存
li_.onclick = function (){
this.style.color = 'red';
}
father.appendChild(li_);
}
// ====================================================================
// 事件委托,即使li自己本身没有点击事件,但是当点击了li的时候,点击事件就会一直向上
// 传递,触发父亲的点击事件,依次类推,这个就是事件冒泡, 事件委托一般要结合ev.target来使用
// target: 触发事件的最早的元素,即源元素, 这里可以是 一个具体的li标签,代表真正点击到的元素
// currentTarget: 事件处理程序附加到的元素,代表事件绑定对象的元素,
// 但是,如果现在直接点击ul标签,那么就相当于点了所有的li标签,所有的标签都会变色
// 同时li标签里面不能有其他的标签,否者target的指向就会发生变化
let ul = document.querySelector('ul');
ul.onclick = function (ev){
ev.target.style.background = 'green';
}
// mouseover 和 mouseenter 的区别
let new_div = document.createElement('div');
let inner_div = document.createElement('div');
inner_div.style.cssText = 'width:100px;height:100px;background:blue;margin-top:20px;';
new_div.style.cssText = 'width:400px;height:150px;background:pink;margin:10px';
new_div.innerHTML = 'mouseover and mouseenter 区别';
new_div.appendChild(inner_div);
document.body.appendChild(new_div);
// onmouseenter 是不冒泡的, 或者说mouseenter是不接受事件冒泡的
// 给最外面的那一个box添加一个onmouseenter事件
// 当这个元素进入到内部的时候,只会触发一次,即使再次移动到子元素的内部也不会触发
// new_div.onmouseenter = function (){
// this.innerHTML += '-enter!=';
// }
// mouseover 相当于存在事件冒泡, 所以事件委托只能用于要冒泡的东西上面
// 冒泡就是子元素的事件传递到父元素的过程
// 但是,如果是mouseover,当鼠标进入到wrapper容器的时候,会触发一次,当鼠标移动到inner box的时候
// 鼠标会触发一次,当一直在innerbox里面移动的时候,会一直触发mouseover事件,除非将鼠标移动到wrapper
// 容器,这时无论怎样都不会触发
// new_div.onmouseover = function (){
// this.innerHTML += '-over!='
// }
// blur, focus, unload, onmouseenter, onmouseleave, 这些事件是不冒泡的
// 只有当触发被绑定的元素的时候才会发生变化
// focusin , focusout, 支持冒泡
// 可以通过打印 event.bubbles 来判断是否支持事件冒泡(boolean);
9、数据类型判断
function typeOf(obj){
let res = Object.prototype.toString.call(obj).split(' ')[1];
res = res.substring(0, res.length-1).toLowerCase();
return res;
}
console.log(typeOf([]));
// array
console.log(typeOf({}));
// object
console.log(typeOf(new Date()));
// date
10、Symbol符号类型
// symbol 就是一种类似于字符串的数据类型,唯一的特点就是
// 被定义之后就是唯一的字符窜名称,不会出现第二个, 就是唯一的数据
// 定义, 在这里不需要new关键字,直接定义就可以了
// 第一种定义方式会在内存中开辟一个新的地址空间,导致每一次定义以后结果都是不一样的
let s = Symbol('这个是一个描述');
let s1 = Symbol('这个是一个描述');
console.log(s === s1);
// false
// 普通方式定义之后获得符号的描述
console.log(s.description);
// 这个是一个描述
// 第二种定义方式会首先在全局寻找是否被定义过
// 如果被定义过,那么新的变量将指向同一块内存地址
// 否者就会创建一个新的内存地址空间
let str1 = Symbol.for('name');
let str2 = Symbol.for('name');
console.log(str1 === str2);
// true
// 此时可以通过以下方式获取到符号的描述
let des = Symbol.keyFor(str2);
console.log(des);
// name
// symbol 解决字符窜耦合的问题
let grade = {'李四': {js: 100, css: 55}, '李四': {js: 100, css: 55}};
console.log(grade);
// { '李四': { js: 100, css: 55 } }
// 这时候第二个属性将会把前面的属性覆盖掉,导致最后只有一个李四
let user1 = {name: '张三', key: Symbol()};
let user2 = {name: '张三', key: Symbol()};
// 如果要在对象里面的属性用变量的话要使用[].计算表达式
let new_grade = {[user1.key]: {js: 100, css: 55}, [user2.key]: {js: 100, css: 55}};
console.log(new_grade);
// { [Symbol()]: { js: 100, css: 55 }, [Symbol()]: { js: 100, css: 55 } }
console.log(user1.name, new_grade[user1.key]);
// 张三 { js: 100, css: 55 }
// symbol 在缓存容器中的使用
class Cache{
static data = {};
static set(name, value){
return (Cache.data[name] = value);
}
static get(name){
return Cache.data[name];
}
}
Cache.set('key', 'value');
console.log(Cache.get('key'));
// value
let user = {name: 'apple', desc: '用户', key: Symbol('会员资料')};
let cart = {name: 'apple', desc: '购物车', key: Symbol('购物车数据')};
Cache.set(user.key, user);
Cache.set(cart.key, cart);
console.log(Cache.get(user.key));
// { name: 'apple', desc: '用户', key: Symbol(会员资料) }
// 扩展属性和对象属性保护
let sym = Symbol('这是一个符号类型');
let dic = {
name: '你好',
[sym]: '符号属性'
}
// 符号类型不能被普通对1方式遍历到
for (let key in dic){
console.log(key);
}
// name
for (let key of Object.keys(dic)){
console.log(key);
}
// name
// 如果想要使用符号类型,必须通过以下方式来遍历,这样只能遍历到符号类型
for (let key of Object.getOwnPropertySymbols(dic)){
console.log(key);
}
// Symbol(这是一个符号类型)
// 如果想要遍历所有属性,可以通过以下方式进行遍历
for (let key of Reflect.ownKeys(dic)){
console.log(key);
}
// name
// Symbol(这是一个符号类型)
11、Map和WeekMap
// map 类型的特点以及创建方法
// 如果在对象中属性是一样的,那么只会保留一个, ‘name' 和 name 默认是一样的
// map 的 key 对象可以是函数,字符窜,对象,都可以
let map = new Map();
map.set('name', 'value');
map.set(function (){}, 'value1');
map.set('1', 'value2');
// 通过构造函数创建
let map1 = new Map([['key', 'value'], ['k1', 'value1']]);
// 同时map的set方法支持链式操作
// map 类型的增加,删除,改变,查找
let obj = {};
let map2 = new Map();
map2.set(obj, 'value for map 2');
console.log(map2.get(obj));
// value for map 2
console.log(map2.delete(obj));
// true
// 可以通过 clear 函数来实现清空操作
// 通过 has 方法来实现检测是否含有键
// 遍历map数据类型
let map3 = new Map([['key', 'value'], ['k1', 'value1']]);
console.log(map3.keys());
// [Map Iterator] { 'key', 'k1' }
console.log(map3.values());
// [Map Iterator] { 'value', 'value1' }
console.log(map3.entries());
// [Map Entries] { [ 'key', 'value' ], [ 'k1', 'value1' ] }
// map 类型转换操作
let map4 = new Map([['key', 'value'], ['k1', 'value1']]);
console.log([...map4]);
// [ [ 'key', 'value' ], [ 'k1', 'value1' ] ]
// map 管理dom节点
// let map5 = new Map();
// document.querySelectorAll('div').forEach(item => {
// map5.set(item, {
// content: item.getAttribute('name')
// });
// })
// map5.forEach((config, ele)=>{
// ele.addEventListener('keyup', ()=>{});
// })
// weekMap 的使用, 弱引用类型支持的方法比较少,不可迭代
let w_map = new WeakMap();
// 这里传入的key必须是一个对象,否者会报错,或者说传入的必须是一个内存地址
let arr = []
w_map.set(arr, 'value');
console.log(w_map.get(arr));
// value
console.log(w_map.has(arr));
// true
// 弱引用类型体验
// 这时候内存会开辟一个新的地址空间 {name: 'hd'},
// 然后指针指向或者说引用变量会指向或引用这块内存空间,当期的指针就是 hd
let hd = {name: 'hd'};
// 这里说引用吧,其实都是差不多的
// 然后有创建了一个引用(指针):指向相同的内存空间
let new_pointer = hd;
// 将原来的指针清空
hd = null;
// 这样就只有 new_pointer 指向那个内存空间
// 然后将新的指针清空,这样就没有指针指向那个内存空间,这样,那一个内存空间就成了’垃圾‘;
// 判断一个内存是否被引用就是判断引用计数器的值是否为0
// 每次引用一次,引用计数器的值就会 +1
new_pointer = null;
// 但是现在如果是弱映射,那么引用计数器的值就不会添加
let pointer = {name: 'key'};
let w_map1 = new WeakMap();
w_map1.set(pointer, 'value of added');
pointer = null;
// 结果 key 的指向被清空,那么里面的对应的 value 数据也会被清空
console.log(w_map1);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WeekMap示例</title>
<style>
.wrapper {
display: flex;
margin: 10px;
border: 1px solid rgba(0, 0, 0, .5);
width: 50%;
justify-content: space-between;
padding: 10px;
}
.wrapper > div {
width: 50%;
}
.wrapper ul {
list-style: none;
margin: 0;
padding: 0;
}
.wrapper ul > li {
border: 1px solid red;
margin: 10px;
padding: 2px 5px 2px 7px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
}
ul > li > a {
display: inline-block;
width: 18px;
height: 18px;
color: white;
background-color: green;
text-align: center;
align-items: center;
font-weight: bold;
border-radius: 3px;
}
.wrapper .col-line {
width: 1px;
height: 110px;
background-color: gray;
margin: 0 10px;
}
.wrapper h5 {
margin: 0 0 10px;
padding: 0;
}
.wrapper > div:last-child > p > span {
display: inline-block;
background-color: green;
color: whitesmoke;
padding: 5px;
border-radius: 10%;
margin: 3px;
}
</style>
</head>
<body>
<div class="wrapper">
<div>
<ul>
<li><span>php</span><a>+</a></li>
<li><span>js</span><a>+</a></li>
<li><span>python</span><a>+</a></li>
</ul>
</div>
<div class="col-line"></div>
<div>
<h5 id="count">共选了0门课程</h5>
<p id="list">
</p>
</div>
</div>
<script>
class Lesson {
constructor() {
this.list = document.querySelectorAll('ul > li');
this.count_ele = document.getElementById('count');
this.list_ele = document.getElementById('list');
this.map = new WeakMap();
}
run() {
this.list.forEach(li => {
li.querySelector('a').addEventListener('click', ev => {
const a = ev.target;
// const li_ = ev.target.parentElement
const status = li.getAttribute('select');
if (status) {
li.removeAttribute('select');
this.map.delete(li);
a.innerHTML = '+';
a.style.backgroundColor = 'green';
} else {
li.setAttribute('select', 'true');
a.innerHTML = '-';
a.style.backgroundColor = 'red';
this.map.set(li, undefined);
}
this.render();
})
})
}
render() {
this.count_ele.innerHTML = `共选了${this.count()}门课程`;
this.list_ele.innerHTML = this.lists();
}
lists() {
return [...this.list].filter(li => {
return this.map.has(li);
}).map(li => {
return `<span>${li.querySelector('span').innerHTML}</span>`
}).join('')
}
count() {
return [...this.list].reduce((count, li) => {
return (count += this.map.has(li) ? 1 : 0);
}, 0);
}
}
let lesson = new Lesson();
lesson.run();
</script>
</body>
</html>