作业一: https://github.com/hiszm/demo65/blob/main/README.md

作业二:https://hiszm.github.io/demo65/

一、js数据类型

<!-- 
typeof  123   //Number
typeof  'abc'  //String
typeof  true    //Boolean
typeof  undefined  //Undefined
typeof  null    //Object
typeof  { }      //Object
typeof  [ ]      //Object
typeof  console.log()    //Function
 -->
<script>
console.log(typeof(123)) 
function getType(data){
    let type = typeof data;
    if(type !== "object"){
      return type
    }
    return Object.prototype.toString.call(data).replace(/^\[object (\S+)\]$/,'$1')
}

二、数组增删改查

注意坑: 删除的插入增加的时候数组前中后

1、push()

可接收任意数量的参数,把它们逐个添加至数组末尾,并返回修改后数组的长度。例如:

var arr = [];
var len = arr.push(1);
console.log(arr); // [1]
console.log(len); // 1
len = arr.push(2,3);
console.log(arr); // [1,2,3]
console.log(len); // 3

2、unshift()

该方法与push()类似,也可接收任意数量的参数,只不过是将参数逐个添加至数组前端而已,同样返回新数组长度。咱们接着上面的例子:

var len = arr.unshift(0);
console.log(arr); // [0, 1, 2, 3]
console.log(len); // 4
len = arr.unshift(-2,-1);
console.log(arr);  // [-2, -1, 0, 1, 2, 3]
console.log(len);  // 6

3、concat()

该方法与push()方法有点类似,同样是将元素添加至数组末尾,只不过这个数组已经不是原来的那个数组了,而是其副本,所以concat()操作数组后会返回一个新的数组。具体用法如下:

① 不传参数,返回当前数组副本

② 传递一或多个数组,则该方法会将这些数组中的每一项都添加到结果数组中

③ 传递非数组参数,这些参数就会被直接添加到结果数组的末尾

继续接着上面的栗子:

var arr1 = arr.concat(4,[5,6]);
console.log(arr);  // [-2, -1, 0, 1, 2, 3]
console.log(arr1);  // [-2, -1, 0, 1, 2, 3, 4, 5, 6]

例子中一目了然,原数组保持不变,新数组后面添加了4、5、6三个元素。

4、splice()

前面的三个方法都具有很大局限性,因为不是添加到数组前就是数组后,而splice()就不一样了,它非常灵活和强大。灵活是因为它可以添加元素到数组的任意位置,强大是因为它除了可以添加元素之外还具有删除和替换元素的功能(这个后面会陆续讲到)。

splice()可以向数组指定位置添加任意数量的元素,需要传入至少3个参数: 起始位置、0(要删除的元素个数)和要添加的元素。

依然接着上面的例子继续:

arr.splice(3,0,0.2,0.4,0.6,0.8);
console.log(arr); // [-2, -1, 0, 0.2, 0.4, 0.6, 0.8, 1, 2, 3]

可以看出,splice()与push()和unshift()一样是直接在原数组上修改的。

1、pop()

与push()方法配合使用可以构成后进先出的栈,该方法可从数组末尾删除最后一项并返回该项。

接着上例:

var item = arr.pop();
console.log(item);  // 3
console.log(arr);  // [-2, -1, 0, 0.2, 0.4, 0.6, 0.8, 1, 2]

2、shift()

与push()方法配合使用可以构成先进先出的队列,该方法可删除数组第一项并返回该项。

继续接着上例:

var item = arr.shift();
console.log(item); // -2
console.log(arr); // [-1, 0, 0.2, 0.4, 0.6, 0.8, 1, 2]

3、slice()

该方法同concat()一样是返回一个新数组,不会影响原数组,只不过slice()是用来裁剪数组的,返回裁剪下来的数组,具体用法如下:

slice()方法可以接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下,slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项——但不包括结束位置的项。

咱们还是继续接着上面例子吧~~

var item = arr.shift();
console.log(item); // -2
console.log(arr); // [-1, 0, 0.2, 0.4, 0.6, 0.8, 1, 2]

4、splice()

好,继续讲这个“万能”的方法。

上面讲到,该方法在添加数组元素的时候需要传入3个以上参数,而其中第2个参数就是用于指定要删除元素的个数的,那时我们传的是数字0。那么,如果单单只需删除元素,我们就只需给splice()传入两个参数,第1个参数用于指定要删除的第一项的位置,第2个参数用于指定要删除元素的个数。

继续上例~~

arr.splice(2,4);
console.log(arr); // [-1, 0, 1, 2]

从索引项为2的位置开始删除4个元素,所以结果为 [-1, 0, 1, 2]。

这个其实最灵活的方式就是直接使用splice()这个强大的方法了,其实通过以上对该方法的了解,我们大致就能知道使用该方法修改数组元素的基本原理。

原理很简单,就是向指定位置插入任意数量的元素,且同时删除任意数量的元素。

依然继续上例~~

arr.splice(2,1,0.5,1,1.5);
console.log(arr);  // [-1, 0, 0.5, 1, 1.5, 2]

indexOf()和lastIndexOf()

这两个方法都接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中,indexOf()从数组的开头(位置0)开始向后查找,lastIndexOf()方法则从数组的末尾开始向前查找。

例如:

var index = arr.indexOf(0);
console.log(index);  // 1
index = arr.indexOf(3,0);
console.log(index);  // -1

当找不到该元素时,返回 -1 ,lastIndexOf()方法同理

三、DOM数据结构 和常用的api

增删改查-注意父子也即是前后

DOM的结构

img

img

常用语法

//1.定义对象: var const let

```var Person = {   name: ‘张三’,   birth,//等同于birth: birth   hello() { console.log(‘我的名字是’, this.name); }// 等同于hello: function ()… };




/**/2.对象的合并assign:**

**2.1.添加属性\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\***

var target = { a: 1 };//合并成: {a:1, b:2, c:3},注:同名属性,则后面的属性会覆盖前面的属性。 var source1 = { b: 2 }; var source2 = { c: 3 };

Object.assign(target, source1, source2);


**2.2.添加方法\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\***

Object.assign(SomeClass.prototype, {   someMethod(arg1, arg2) {     ···   },   anotherMethod() {     ···   } }); // 等同于下面的写法 SomeClass.prototype.someMethod = function (arg1, arg2) { ··· }; SomeClass.prototype.anotherMethod = function () { ··· };


**2.3.克隆对象\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\**\****


var obj1 = {a: {b: 1}};//obj1.a.b = 2; var obj2 = Object.assign({}, obj1);//克隆对象obj1,得到:obj2.a.b=2

//只能克隆原始对象自身的值,不能克隆它继承的值。 class Point {   constructor(x, y) {     Object.assign(this, {x, y});   } } //解决方案 function clone(origin) {   let originProto = Object.getPrototypeOf(origin);   return Object.assign(Object.create(originProto), origin); }

**2.4.合并多个对象**

const merge = (target, …sources) => Object.assign(target, …sources); const merge = (…sources) => Object.assign({}, …sources);

**2.5.为属性指定默认值,即2.1的扩展**

const DEFAULTS = {   logLevel: 0,   outputFormat: ‘html’ };

function processContent(options) {   options = Object.assign({}, DEFAULTS, options);   console.log(options);   // … }

**【实践操作:】**

//1.获取key value组合成数组:js ES6 var obj = {   ”name” : “zh”,   ”age” : 22, } 1.1.对象自身属性遍历 for(var key in obj){   console.log(key); //键名   console.log(obj[key]); //键值   //if(obj.hasOwnProperty(key))

  if (obj.hasOwnProperty(key) === true) {     console.log(obj[key])   }

}

const keys = Object.keys(obj); const values = Object.values(obj);

1.2.解构赋值 let { x, y, …z } = { x: 1, y: 2, a: 3, b: 4 }; x // 1 y // 2 z // { a: 3, b: 4 }

1.3.扩展运算符 let z = { a: 3, b: 4 }; let n = { …z }; n // { a: 3, b: 4 }

//2.取值判断问题

2.1.读取对象内部的某个属性,往往需要判断一下该对象是否存在 const firstName = message && message.body && message.body.user && message.body.user.firstName) || ‘default’;

if(firstName){

} 2.2.现在有一个提案,引入了“Null 传导运算符”

const firstName = message?.body?.user?.firstName   ‘default’;

if(firstName){

}


#### Oject增删改查



1、定义(增)属性
1.1、使用冒号可以为对象定义属性:

> var obj = {name:'erha',age:18};    //冒号左侧是属性名,右侧是属性值。属性与属性之间通过逗号运算符进行分隔

1.2、可以通过 点运算符 在结构体外定义属性:

> var obj = {name:'erha',age:18};
> obj.gender = 2;      //定义对象的属性
> console.log(obj);    //{name:'erha',age:18,gender:2}



1.3、通过 Object.defineProperty() 方法定义对象属性:

先创建一个对象直接量 obj,然后使用 Object.defineProperty() 函数将数据属性添加到用户定义的 obj 对象中。定义属性 newDataProperty,值为101,可写,可枚举,可修改特性。

> var obj = {};
> Object.defineProperty(obj,"newDataproperty",{
>     value:101,
>     writable:true,
>     enumexable:true,
>     configurable:true
> });
> obj.newDataProperty = 102;
> document.write(obj.newDataProperty);    //102
>
> 

1.2、通过 Object.defineProperties() 方法定义对象的多个属性:

使用 Object.defineProperties() 函数将数据属性和访问器属性添加到用户定义的对象 obj 上。使用对象文本创建具有 newDataProperty 和 newAccessorProperty 描述符对象的 descriptors 对象。

> var obj = {};
> Object.defineProperties (obj,{
>     newDataProperty:{
>         value:101,
>         writable: true,
>         enumerable: true,
>         configurable: true
>     },
>     newAccessorProperty:{
>         set: function (x){
>             this.newaccpropvalue = x;
>         },
>         get: function ){
>             return this.newaccpropvalue;
>         },
>         enumerable; true,
>         configurable; true
>     }
> });
> obj.newAccessorProperty = 10;
> document.write(obj.nexAccessorProperty);    //10



2、访问(读)属性
2.1、通过 点运算符 可以访问对象属性:

> var obj = {name:'erha',age:18};
> console.log(obj.name);        //erha
> console.log(obj.['name']);    //erha

> let name = 'age';
> console.log(obj.[name]);      //18    因为此时name是变量,它的值是 'age'

> console.log(obj.__proto__);    //{constructor:...,toString:...}
> 注意:obj.name 等价于 obj['name'],而 obj.name 不等价于 obj[name]。因为点运算符后的 name 是字符串,而不是变量。

2.2、通过 Object.keys() 方法可访问对象的 私有属性:

> var obj = {name:'erha',age:18};
> console.log(Object.keys(obj));       //{'name','age'}
> console.log(Object.values(obj));     //{'erha',18}
> console.log(Object.entries(obj));    //{[name,'erha'],[age,18]}

> console.dir(obj);    //查看所有属性:{name:'erha',age:18,__proto__:......}



2.3、可以通过 obj.hasOwnProperty() 判断一个属性是否为对象的私有属性:

> var obj = {name:'erha',age:18};
> console.log(obj.hasOwnProperty('toString'));    //false,说明toString属性并不是obj的私有属性
> console.log('toString' in obj);    //true,这种 in 运算符不能分清属性是对象的私有属性还是原型上的属性

2.4、可以通过 Object.getPrototypeOf() 返回指定对象的原型

> function Pasta(grain,width){
>     this.grain = grain;
>     this.width = width;
> }
> var spaghetti = new Pasta("wheat",0.2);    //实例化一个Pasta函数对象,此时spaghetti的__proto__指向Pasta函数的原型
> var proto = Object.getPrototypeOf(spaghetti);
> document.write(proto === Pasta.prototype);    //true    只有函数对象有prototype属性

3、增加/赋值(写)属性
3.1、使用 点运算符 和 数组运算操作 直接赋值:

> var obj = {name:'erha',age:18};
> obj.name = hashiqi;      //设置对象的新值,将覆盖原来的值
> obj['age'] = 100;        //设置对象的新值,将覆盖原来的值

> let key = 'age';
> console.log(obj[key]);    //100





3.2、可以通过 Object.assign() 方法进行批量赋值:( 改方法是 ES6 新增的 API )

> var obj = {name:'erha',age:18};
> Object.assign(obj,{name:'keji',age:22,gender:1});

> console.log(obj);    //{name:'keji,age:'22',gender:1}



3.3、无法通过修改自身属性影响原型上的属性:

> var obj = {name:'erha',age:18};
> obj.toString = 123;    //这种方式只会修改自身属性,并不会影响obj原型上的toString属性

> //如果一定要修改或增加原型上的属性,可以通过如下方式
> obj.proto.toString='xxx';        //不推荐用__proto__
> Object.prototype.toString='xxx'; //一般不要修改原型,会引起很多问题





3.4、可以通过 Object.create() 方法直接指定对象的原型:( 改方法是 ES6 新增的 API )

> let common = {country:China,color:yellow};

> let person = {};
> person.__proto__ = common;    //不推荐

> let person = Object.create(common);    //这种方式会直接将person的原型指向到common
> person.name = 'lixiaolong';    //对person进行赋值
> person.age = '18';             //可以在create方法中对person直接赋值,但是value书写比较麻烦



4、删除(删)属性
4.1、将对象的某个属性值赋值为 undefined:

> var obj = {name:'erha',age:18};
> obj.name = undefined;      //将obj对象的name属性赋值为undefined
> console.log(obj);          //打印结果:{name:undefined,age:18}
> console.log('name' in obj);  //true,表示obj对象中仍然含有name属性,只是属性值为undefined
> console.log(obj.hasOwnProperty('name'));    //false    表示此时的obj不含有私有属性nameconsole.log(obj.name);     //undefined    注:不能通过这种方式判断obj是否含有name属性

4.2、使用 delete 运算符删除对象的某个属性:(delete  操作符不能直接删对象)

> var obj = {name:'erha',age:18};
> delete obj.name;       //删除obj对象的name属性,也可写成:delete obj['name']
> console.log(obj);      //打印结果:{age:18}
> console.log('name' in obj);  //false,表示obj对象中没有name属性
> console.log(obj.hasOwnProperty('name'));    //false    表示此时的obj不含有私有属性name

> console.log(obj.name);     //undefined    注:不能通过这种方式判断obj是否含有name属性

以上两种删除方式的区别是:当通过 delete 删除对象的属性之后,不是将该属性值设置为 undefined,而是从对象中彻底清除属性。注意:obj.name === undefined 不能断定 name 是否为 obj 的属性,可以通过:'name' in obj && obj.name === undefined;判断 name 是否为 obj 的属性。




### 四、js进行深层次的复制


#### 浅复制
- 区分类型的复制、复杂的数据类型进行递归检测

```html 
// 用jQuery进行对象复制

// 在可以使用jQuery的情况下,jQuery自带的extend方法可以用来实现对象的复制。



a = {k1:1, k2:2, k3:3};

b = {};

$.extend(b,a);



- 转json方法如下(仅适用json那样的对象或者数组,

//如果对象或者数组中有类似Date,

//Function这种是不适用的(推荐插件lodash的cloneDeep))

const obj = {
  key1: 'value1',
  key2: 'value2',
  key3: ['index1'],
  key4: {
    subKey1: 'subValue1'
  }
}
const obj2 = JSON.parse(JSON.stringify(obj))

1 try {
2   const obj2 = JSON.parse(JSON.stringify(obj))
3 } catch (error) {
4   const obj2 = {}
5 }

深度复制

1.下面的方法,是给Object的原型(prototype)添加深度复制方法(deep clone)。

Object.prototype.clone = function() {
    // Handle null or undefined or function
    if (null == this || "object" != typeof this)
        return this;
    // Handle the 3 simple types, Number and String and Boolean
    if(this instanceof Number || this instanceof String || this instanceof Boolean)
        return this.valueOf();
    // Handle Date
    if (this instanceof Date) {
        var copy = new Date();
        copy.setTime(this.getTime());
        return copy;
    }
    // Handle Array or Object
    if (this instanceof Object || this instanceof Array) {
        var copy = (this instanceof Array)?[]:{};
        for (var attr in this) {
            if (this.hasOwnProperty(attr))
                copy[attr] = this[attr]?this[attr].clone():this[attr];
        }
        return copy;
    }
    throw new Error("Unable to clone obj! Its type isn't supported.");
}

2 使用额外的工具函数实现,适用于大部分对象的深度复制(Deep Clone)。

function clone(obj) {
    // Handle the 3 simple types, and null or undefined or function
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    // Handle Array or Object
    if (obj instanceof Array | obj instanceof Object) {
        var copy = (obj instanceof Array)?[]:{};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr))
              copy[attr] = clone(obj[attr]);
        }
        return copy;
    }
    throw new Error("Unable to clone obj! Its type isn't supported.");
}	

五、IE与W3C区别和参数,以及IE的even的区别

两者区别

img

事件

Dom0级模型 又称为原始事件魔性, 在该模型中, 事件不会传播, 即没有事件流的概念. 事件绑定监听函数比较简单, 有两种方式

// 1. html代码中直接绑定
<input type = "button" onclick = "fun()">

// 2. 通过js代码指定属性值
var btn = document.getElementById('btn');
btn.onclick = fun;

// 移除监听函数
btn.onclick = null;
var btn = document.getElementById('.btn');
btn.attachEvent(‘onclick’, showMessage);
btn.detachEvent(‘onclick’, showMessage);

事件冒泡流 IE 的事件流叫做事件冒泡,即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。以下面的 HTML 页面为例。也就是说,click 事件首先在 div元素上发生,而这个元素就是我们单击的元素,然后 click 事件沿着 dom 树向上传播,在每一级节点都或发生,直至传播到 document 对象

事件的三个参数

添加事件处理函数 addEventListener

参数 1 指定事件名称…click mouseover mouseout

参数 2 事件处理程序(匿名函数或者有名函数)

参数 3 true(捕获阶段发生) or false(冒泡阶段发生)

六、原生JS实现事件委托

概念

它还有一个名字叫事件代理,JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

实现

<ul id="ul">
        <li>0</li>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
    </ul>
    <button id="btn">点我添加一个li</button>
    
    
    
    // 事件委托具体实现
var ul = document.getElementById("ul");
    ul.onclick = function (event) {
        event = event || window.event;
        var target = event.target;
        // 获取目标元素
        if (target.nodeName == 'LI') {
            alert(target.innerHTML);
        }
    }
    // 为按钮绑定点击事件
    var btn = document.getElementById('btn');
    btn.onclick = function () {
        var li = document.createElement('li');
        // 新增li的内容为ul当前子元素的个数
        li.textContent = ul.children.length;
        ul.appendChild(li);
    }

预览