JS进阶笔记

220 阅读8分钟

JS进阶

补充1:call、apply、bind的用法

原本this指向:谁调用的方法,this就指向谁

作用:调用函数、改变this指向、借用别的对象的方法

第一个参数为null或' ',this指向window对象

var name = "window中的name"
function getName(){
    name = "function中的name"
    cconsole.log(this.name);
}
getName();//此处为function中的name,若function中没有name则到window中找
getName.apply();

区别:call和bind传参是单个参数,apply传参是数组参数;call和apply直接调用函数,直接生效,bind返回一个新的方法,需要另行调用。

补充2:this指向问题

注意:this在strict模式下指向不是window对象

1、通常情况

一般来说:一般函数中的this指向window对象
function a(){
    var user = "小明";
    console.log(this.user); //undefined
    console.log(this);  //Window
}
window.a();

2、对象中的方法

指向调用函数者(人为改变this指向除外)
var o = {
    user:"小明",
    fn:function(){
        console.log(this.user);  //小明
    }
}
//var o = {}
//Object.defineProperty(o,"user",{
//  value:"小明"
//});
o.fn();

注意:以下这种情况,this指向window对象

var j = o.fn;
j();

3、实例对象调用构造函数

指向实例对象
function Person(age, name) {
         this.age = age;
         this.name = name
         console.log(this)  // 此处 this 分别指向 Person 的实例对象 p1 p2
     }
var p1 = new Person(18, 'zs')
var p2 = new Person(18, 'ww')

注意:原型对象如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。

function fn()  
{  
    this.user = '小明';  
    return {}; 
    //return function(){};
    //return 1;
    //return undefined;
    //return null;
}
var a = new fn;  
console.log(a.user); //undefined、undefined、小明、小明、小明

4、通过事件绑定的方法

this 指向绑定事件的对象
var oBtn = document.getElementById("btn");
    oBtn.onclick = function() {
        console.log(this); // btn
    }

5、定时器函数

此时 this 指向 window
 setInterval(function () {
       console.log(this); // window
     }, 1000);

1.创建对象

属性命名规则 私有成员:user,伪私有成员:user,普通成员:user

1-1、普通对象创建方法
var obj = {
    name:"小明",
    set:function(){
        console.log(this.name);
    }
}
1-2、基于Object构造函数
var obj = new Object();
obj.name = "小明"
obj.set = function (){
    console.log(this.name)
}
1-3、基于对象字面量
var obj = {
    name:"小明",
    getName:function(){
        console.log(this.name);
    },
    address:{
        name:"上海",
        tel:"110",
    },
}
1-4、基于工厂模式批量创建对象
function createObj(name,address){
    var obj = new Object();
    obj.name = name;
    obj.getName = function(){
    console.log(this.name)
    }
    obj.address = address;
    return obj;
}
var person = createObj("小明",{
    name:"上海",
    tel:"110",
})
1-5、基于构造函数创建对象

通过this为对象添加属性,用new操作符为对象创建实例

function createObj(name,address){
    this.name = name;
    this.getName = function(){
    return this.name;
    }
    this.address = address;
}
var person = new createObj("小明",{
    name:"上海",
    tel:"110",
})
1-6、基于原型对象的模式

将所有函数和属性都封装到原型对象的prototype属性上

function Person(){
    Person.prototype.name = "小明";
    Person.prototype.getName = function(){
    return this.name;
    }
    Person.prototype.address = {
        name:"上海",
        tel:"110",
    };
}
var person = new Person();
var person2 = new Person();
1-7、构造函数和原型混合使用的模式(比较好的方法)
//构造函数方法定义对象中的属性
function Person(name,address){
    this.name = name;
    this.address = address;
}
//在原型中添加所有实例共享的函数
Person.prototype.getName = function(){
    return this.name;
}
//生成实例
var person = new Person("小明",{
    name:"上海",
    tel:"110",
})
1-8、动态原型模式
function Person(name,address){
    this.name = name;
    this.address = address;
    if(typeOf Person._initialized === "undefined"){
    Person.prototype.getName = function(){
        return this.name;
        };
        Person._initialized = true;
    }
}
//生成实例
var person = new Person("小明",{
    name:"上海",
    tel:"110",
})

2.对象克隆

克隆:将某个变量的值复制到另一个变量上,根据原始变量和复制的变量的值之间相互影响的情况,分为深克隆和浅克隆。

对于不同的数据类型(基本数据类型和引用数据类型),深浅克隆也会有不同的表现。

基本数据类型:变量存放的是变量本身的值,可以直接访问

引用数据类型:变量存放的是值在内存中的地址,地址指向内存中的某个位置,如果多个变量指向同一位置,那么对变量进行修改会影响多个变量的值

深克隆与浅克隆 区别:浅克隆的原始变量和克隆变量互相影响,深克隆的原始变量和克隆变量相互独立不受影响

2-1、简单的引用复制(浅克隆)
var a = {
    name:"小明",
    age:18,
}
var b = a;
​
2-2、ES6的Object.assign()函数(浅克隆)

作用:将原对象的可枚举属性赋值到目标对象中

var result = Object.assign({},origin)
2-3、JSON序列化和反序列化(深拷贝)

深拷贝还有:arr.concat、arr.slice(缺点:都只能深拷贝一层的数据)

如果一个对象中所有属性都是可序列化的,可以使用JSON.stringify()函数将原始对象序列化为字符串,再使用JSON.parse()函数将字符串反序列化为一个对象。

var origin = {
    a:1,
    b:[2,3,4],
    c:{
        d:"name",
    }
}
var result = JSON.parse(JSON.stringify(origin))

这种方法有几个问题:

  • 无法实现对函数、RegExp等特殊对象的深拷贝
  • 对象的constructor会被抛弃,所有构造函数会指向Object,原型链关系会被破坏
  • 对象中存在循环引用会抛出异常
(function(_){
          var types = "Array Object Number String Date RegExp Boolean Null Undefined".split(" ")
        // console.log("type",type);
        function type(){
​
        }
        for(var i=0;i<types.length;i++){
          _['is'+types[i]] = (function(){
            // console.log("_",_);
            return function(param){
              return type.call(param)
            }
          })(types[i])
        }
        return _;
      })(_={})

### 原型对象

原型对象、构造函数、实例对象

通过new操作符创建一个实例对象,它的 proto 属性指向构造函数的原型对象,默认情况下,原型对象的constructor属性指向prototype属性所在的函数(即构造函数)

对象重写

//原来
function Person(){}
Person.prototype.name = "小明"
Person.prototype.age = 18
//重写
function Person1(){}
Person1.prototype = {
    constructor:Person,//加入对象的指向
    name:"小明",
    age:18,
}

原型链

function Person(){}
var person = new Person();
person._proto_ = Person.prototype;
person._proto_._proto_ = Person.prototype._proto_ = Object.prototype;
person._proto_._proto_._proto_ = Object.prototype._proto_ = null;

__proto__是每个对象都有的属性

prototype是函数才有的属性

继承

继承、封装、多态是面向对象语言的三大特性,JavaScript不是一门面向对象的语言,所以不具备继承的特性

function Animal(name){
    this.type = "animal"
    this.name = name
    function sleep(){
        console(this.name+"正在睡觉");
    }
}
function Cat(name){
    this.name = name
    function eat(food){
        console.log(this.name+"正在吃"+food);
    }
}
//原型链继承
Cat.prototype = new Animal();
//将Cat的构造函数指向自身
Cat.prototype.constructor = Cat;
var cat = new Cat("加菲猫");
console.log(cat.type)

//将Cat的构造函数指向自身 Cat.prototype.constructor = Cat;

不写这行代码,Cat的构造函数将指向父类,父类的方法和属性将优先。

缺点:

  1. 原型链继承时,改写了子类Cat的prototype属性,将其指向Animal的一个实例对象,那么所有Cat的实例对象都可以共享Animal实例的属性,如果Animal中有属性的值为引用类型,那么改变Cat实例的值将会改变其他实例的属性值。
  2. 创建子类继承时,无法向父类的构造函数传递参数
  3. 无法实现多继承

构造继承

function Animal(name){
    this.type = "animal"
    this.name = name
    function sleep(){
        console(this.name+"正在睡觉");
    }
}
Animal.prototype.eat = function(food){
    console.log(this.name+"正在吃"+food);
}
function Cat(name){
    //继承
    Animal.call(this);
    this.name = name
}
//Cat实例不能使用原型链上的方法eat

优点:

  1. 引用数据类型不会相互影响
  2. 可以通过多次调用call实现多继承

缺点:

  1. 伪继承,只是子类实例的继承,不是父类的继承 cat instant Cat true,cat instant Animal false
  2. 伪继承,子类的实例不能使用父类原型对象上的属性和方法
  3. 每次子类实例化都要使用call重新绑定this指向

复制继承

主要思想:首先生成父类的实例,通过遍历父类的属性和函数,将其依次设为子类实例或原型对象的属性和函数

function Animal(name){
    this.name = name
    this.sleep = function(){
        console.log(this.name+"正在睡觉");
    }
}
Animal.prototype.eat = function(food){
    console.log(this.name+"正在吃"+food);
}
function Cat(name){
    var animal = new Animal("tom");
    for(key in animal){
        if(animal.hasOwnProperty(key)){
            this[key] = animal[key];
        }else{
        Cat.prototype.key = animal[key];
        }
    }
    return this.name;
}

缺点:

  1. 所有属性都要复制,浪费内存
  2. 实例只是子类的实例,不是父类的实例

#### 组合继承

方法:结合构造继承和原型继承两种方法,在子类的构造函数中通过call调用父类的构造函数,将父类实例的属性和函数绑定到子类的this中,同时,改变子类的prototype属性,继承父类原型对象上的属性和函数。

function Animal(name){
    this.name = name
    this.sleep = function(){
        console.log(this.name+"正在睡觉");
    }
    this.feature = ['fat','thin','tall'];
}
Animal.prototype.eat = function(food){
    console.log(this.name+"正在吃"+food);
}
function Cat(name){
    //通过构造函数继承实例的属性和函数
    Animal.call(this);
    this.name = name
}
//原型链继承
Cat.prototype = new Animal();
//将构造函数绑定到自己身上
Cat.prototype.constructor = Cat;

优点:

  1. 既能继承父类实例的属性和函数,又能继承原型对象的属性和函数

  2. 既是父类的实例,又是子类的实例cat instant Cat true,cat instant Animal true

  3. 不会出现引用属性共享的问题

    在子类的构造函数中将父类的实例属性指向子类的this,即使后面将父类的实例属性绑定到子类的prototype属性上,也会由于构造函数作用域优先级比原型链优先级高,不会出现引用属性共享的问题

  4. 可以向父类的构造函数传递参数

    通过call函数向父类的构造函数传递参数

缺点:

  1. 父类的实例属性绑定了两次

    通过call函数调用了一次父类的构造函数,改写子类的prototype属性,生成父类的实例时又调用了一次父类的构造函数

寄生组合继承

function Animal(name){
    this.name = name
    this.sleep = function(){
        console.log(this.name+"正在睡觉");
    }
    this.feature = ['fat','thin','tall'];
}
Animal.prototype.eat = function(food){
    console.log(this.name+"正在吃"+food);
}
function Cat(name){
    //通过构造函数继承实例的属性和函数
    Animal.call(this);
    this.name = name
}
(function(){
    //设置任意函数Super()
    var Super = function(){}
    //关键:Super函数的原型指向父类Animal的原型,去掉父类的实例属性
    Super.prototype = Animal.prototype;
    //原本是Cat.prototype = new Animal();
    Cat.prototype = new Super();
    Cat.prototype.constructor = Cat;
})();
//实例化时要先animal再cat(先父类后子类)

同步是指:当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续执行下去。 异步是指:当程序1调用程序2时,程序1径自继续自己的下一个动作,不受程序2的的影响。

Object类型及其实例和静态函数

new操作符

HTMLCollection对象和NodeList对象

getElementsByTagName() 方法返回 HTMLCollection对象

HTMLCollection 对象类似包含 HTML 元素的一个数组

length属性:HTMLCollection 对象的 length 属性定义了集合中元素的数量

ps:HTMLCollection 无法使用数组的方法: valueOf(), pop(), push(), 或 join()

NodeList 对象是一个从文档中获取的节点列表 (集合) 。

一些旧版本浏览器中的方法(如:getElementsByClassName() )返回的是 NodeList 对象,而不是 HTMLCollection 对象。

所有浏览器的 childNodes 属性返回的是 NodeList 对象。

大部分浏览器的 querySelectorAll() 返回 NodeList 对象。 相似与区别:

  1. HTMLCollection是 HTML 元素的集合,NodeList 是一个文档节点的集合。
  2. NodeList 与 HTMLCollection 都与数组对象有点类似,可以使用索引 (0, 1, 2, 3, 4, ...) 来获取元素。
  3. NodeList 与 HTMLCollection 都有 length 属性。
  4. HTMLCollection 元素可以通过 name,id 或索引来获取,NodeList 只能通过索引来获取。
  5. 只有 NodeList 对象有包含属性节点和文本节点。

事件流

默认的事件流是冒泡的,默认值为false,从内到外冒泡,改为true变为捕获,从外到内捕获。

btn.addEventListener('click',function(){
    console.log(1);
},true)//捕获

#### 事件处理程序

DOM0级事件处理程序

:事件之间会相互覆盖,

btn.onclick = function(){
    console.log(1);
}

DOM2级事件处理程序

:事件属性之间不会相互覆盖,可以区分冒泡和捕获

btn.addEventListener('click',function(){
    console.log(1);
},true)//捕获

DOM3级事件处理程序

允许自定义事件,自定义事件由createEvent()函数创建,返回的对象有一个initCustomEvent()函数,通过传递对应的参数可以实现自定义事件。

type:字符串,触发的事件类型

bubble:布尔型,表示事件是否可以冒泡

cancelable:布尔型,表示事件是否可以取消

detail:对象,任意类型,保存在Event对象的detail属性中

document.implementation.hasFeature('CostomEvents','3.0');//浏览器是否支持DOM3级事件处理程序

实现步骤:

  1. 创建自定义事件

    var customEvent;
    (function(){
        if(document.implementation.hasFeature('CostomEvents','3.0')){
            var detailData = {name:"小明"};
            customEvent = document.createEvent('customEvent');
            customEvent.initCustomEvent('myEvent',true,false,detailData);
        }
    })();
    
  2. 监听自定义事件

    //获取元素
    var div = document.querySelector('#className');
    //监听myEvent事件
    myEvent.addEventListener('myEvent',function(e)=>{
        console.log(e.detail);
    })
    
  3. 触发自定义事件

    var btn = document.querySelector('#className');
    btn.addEventListener('click',function()=>{
        div.dispatchEvent(customEvent);
    })
    

Event对象

概念: 事件发生后,跟事件相关的一系列信息数据的集合都放在这个对象里,这个对象就是事件对象。如鼠标事件,就会得到鼠标的相关信息。

事件对象的使用:事件触发就会产生事件对象,并且系统会以实参的形式传给事件函数,所以需要形参接收。

<ul><li>123</li></ul>
<script>
var ul = document.querySelector('ul');
ul.addEventListener('click', function(e) {
    // 我们给ul绑定了事件,那么this 就指向ul
    console.log(this);//ul
    // e.target 触发了事件的对象 我们点击的是li e.target 指向的就是li
    console.log(e.target);//li
});
</script>

事件委托

  • 概念: 也称事件代理,不给子元素注册事件,给父元素注册事件,把处理代码放在父元素的事件中执行。

  • 事件委托的原理

    给父元素注册事件,利用事件冒泡,当子元素的事件触发,会冒泡到父元素,然后去控制相应的子元素。

<ul>
        <li>点击1</li>
        <li>点击2</li>
        <li>点击3</li>
    </ul>
    <script>
        // 事件委托的核心原理:给父节点添加侦听器, 利用事件冒泡影响每一个子节点
        var ul = document.querySelector('ul');
        ul.addEventListener('click', function(e) {
            // e.target 这个可以得到我们点击的对象li
            e.target.style.backgroundColor = 'pink';
        })
    </script>

BOM对象

window对象

DOMContentLoaded: 事件触发时,仅当DOM加载完成,不包括样式表,图片等

document.addEventListener('DOMContentLoaded', function() {
            alert(33);
})

window.onresize: 是调整窗口大小加载事件, 当触发时就调用的处理函数。

  1. 只要窗口大小发生像素变化,就会触发这个事件。
  2. 我们经常利用这个事件完成响应式布局。 window.innerWidth 当前屏幕的宽度

location对象

作用: location对象用于获取或设置窗体的URL,并且可以用于解析URL。

URL: 统一资源定位符是互联网上标准资源的地址。——路径

// URL一般的语法格式
// protocol://host[:post]/path/[?query]#fragment
https://study.163.com/course/courseLearn.htm?courseId=11111#/learn/live?lessonId=11111&courseId=11111
组成说明
protocol通信协议;常用的http、ftp、https等
host主机(域名)
port端口号(可选),省略时使用默认端口
path路径,由零或多个‘/'隔开的字符串,表示主机上的一个地址
query参数,以键值对的形式,通过&隔开
fragment片段,#后面内容创建于链接锚点

location对象的属性

属性返回值
href获取或者设置整个URL
host返回主机域名
port返回端口号,没有返回空字符串
pathname返回路径
search返回参数
hash返回片段
location对象方法返回值
assign()跟href一样,可以跳转页面(也成为重定向页面)
replace()替换当前页面(无记录)
reload()重新加载页面(相当于按F5),参数为true时为强制刷新

navigator对象

navigator对象包含有关浏览器的信息,存在很多信息,常用的是 userAgent,该属性可以返回由客户机发送服务器的user-agent头部的值。

1.userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36" // PC
2.userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" // mobile

示例: 判断用户那个终端打开页面,实现跳转

if((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i){ 
    window.location.href = "";  //移动端
} else {   
    window.location.href = "";   //电脑 
}

history对象

window对象给我们提供了一个 history对象,与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中)访问过的URL。

history对象方法作用
back()可以后退
forward()前进
go(参数)前进后退;参数是正整数前进n个页面;否则相反

元素位置

offset元素偏移
offset系列属性作用
element.offsetParent返回该元素带有定位的父级元素,没有则返回body
element.offsetTop返回元素相对定位父元素上方的偏移
element.offsetLeft返回元素相对定位父级左边框的偏移
element.offsetWidth返回自身宽度包括padding、border、width; 返回数值无单位
element.offsetHeight返回自身高度包括padding、border、height; 返回数值无单位
元素可视区 client

client 翻译过来就是客户端,我们使用 client 系列的相关属性来获取元素可视区的相关信息。通过 client系列的相关属性可以动态的得到该元素的边框大小、元素大小等。

  • 属性说明
client系列属性作用
element.clientTop返回元素上边框的大小
element.clientLeft返回元素左边框的大小
element.clientWidth返回自身包括padding、width,不含border,返回值不带单位
element.clientHeight返回自身包括padding、height,不含border,返回值不带单位
元素滚动 scroll
scroll系列属性值作用
element.scrollTop返回被卷去的上侧距离,返回数值不带单位
element.scrollLeft返回被卷去的左侧距离,返回数值不带单位
element.scrollWidth返回自身实际宽度,不含边框,返回数值不带单位
element.scrollHeight返回自身实际高度,不含边框,返回数值不带单位

页面被卷去的头部兼容性解决方案

  1. 声明了 DTD,使用 document.documentElement.scrollTop
  2. 未声明 DTD,使用 document.body.scrollTop
  3. 新方法 window.pageYOffset和 window.pageXOffset,IE9 开始支持
function getScroll() {
    return {
      left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft||0,
      top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
    };
 } 
//使用的时候  getScroll().left

\