Js-随笔学习

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>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值