JavaScript高级总结,看这篇准没错

Javascript高级笔记

1. new创建对象的本质

   function Person(name,age){
     this.name = name;
     this.age = age;
     this.sayHi = function(){
       return "Hello,I'm" + this.name;
     }
   }
   let John = new Person(name,age);
   /**
    * new的过程经过了这几步
    * 1. let obj = {};新建一个对象,开辟空间
    * 2. obj.__proto__ = Person.prototype;将对象的__proto__指向构造函数的prototype
    * 3. Person.call(obj); 执行构造函数中的代码,注意。此时的this为obj
    * 4. 最后返回obj
    * 
   */

2. 几种常见的对象创建方式

1. 字面量创建方式

  let person = {
    name: "孙悟空",
    age: 100,
    sex: 1,
    power: 10000,
    skill: [{
        name: "龟派气功",
        hurt: 10000,
      },
      {
        name: "十倍界王拳",
        hurt: 5000,
      }, {
        name: "元气弹",
        hurt: 999999,
      }
    ]
  };

  person.skill.map(item => {
    console.log(item.name + "," +         item.hurt);
  });

2. 系统构造函数

  let obj = new Object();
  obj.name = "John";
  obj.age = 18;
  obj.sayhi = function(){
    console.log("Hello,I'm John");
  }

以上两种方式每次只能创建一个实例对象,当要创建多个相似对象时,存在代码冗余问题

3. 工厂函数创建对象

  //可以传参数,提高代码复用,
  function createStudent(name,age){
      let student = new Object();
      student.name = name;
      student.age = age;
      student.sayhi = function(){
        console.log("Hello,I'm "+student.name);
     }
      return student;
 }
 	//但是无法判读属于哪个对象。(instanceof)

4. 自定义构造函数

  /**
   * 自定义构造函数可以用instanceof判断属于哪个对象,且解决了代码复用问题,但是每个公共方法都会创建一个实例对象,造成内存浪费。
   */
  function Student(name,age){
    this.name = name;
    this.age = age;
    this.sayHi = function(){
      console.log("Hello,I'm " + this.name);
    }
  }

5. 混合模式(构造函数模式+原型模式)

  /**
  * 原型的作用,共享数据,节省内存,不同实例对象的sayHi方法都指向同一个
  */
  function Student(name,age){
    this.name = name;
    this.age = age;
  }
  Student.prototype = {
    //将自身的prototype指向构造函数
    constructor : Student,
    sayHi:function(){
      console.log("Hello,I'm" + this.name);
    }
  }

3. 原型链

1. 构造函数的原型对象prototype

每个函数function Person()为例,都有一个原型对象prototype,该原型对象默认存在两个属性,一个是constructor指向他的构造函数,即Person(),还有一个__ proto __,指向的是实例化Person的够构造函数的prototype,即Function.prototype,因为function Person() 相当于 let Person = new Function()

    function Person(age) {
      this.age = age;
    }
    let per = new Person(18);
    console.dir(Person);
    console.log(per.__proto__ === Person.prototype);//true

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yp59aLXu-1585295137728)(/Users/mac/Desktop/web/js高级/assets/image/prototype.png)]

2. 实例对象的原型对象__ proto __

首先需要清除new的过程,因为实例化过程就是new Person(),需要清楚new这个过程发生的事情

     * new的过程经过了这几步
     * 1. let obj = {};新建一个对象,开辟空间
     * 2. obj.__proto__ = Person.prototype;将对象的__proto__指向构造函数的prototype
     * 3. Person.call(obj); 执行构造函数中的代码,注意。此时的this为obj
     * 4. 最后返回obj

在new的过程中,obj.__ proto__ 指向构造函数的prototype,所以每个实例对象都存在一个__ __ proto __属性,而这个属性就是指向构造函数的prototype对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VmMdC8F0-1585295137730)(/Users/mac/Desktop/web/js高级/assets/image/原型链.png)]

3.原型链的构成

小总结:

1.每个函数都存在prototype和__ proto __ 。每个实例对象都存在__ proto __ 。

2.原型链就是实例对象的__ proto __ 指向构造函数的prototype ,构造函数的prtotype.__ proto **指向Object.prototype,最后的Object.prototype. proto __指向null**,而只要是在这条链上的属性或方法,

实例对象都能访问

在js中,只要是在原型链(prototype)上的属性,在哪个环节都可以任意的调用,例子↓

	//构造函数
	function Person(name){
    this.name = name;
  }
	//在构造函数原型对象上添加方法
	Person.prototype.run = function(){
    console.log("跑步");
  }
	
	//在Function上定义一个方法
	Object.prototype.printfName = function(){
    console.log(this.name);
  }

	//实例化对象
	let per = new Person("GZH");
	
	//此时per可以调用run()方法
	per.run();//跑步
	
	//也可以调用printfName方法,因为prototype是new Object(),也就是prototype是Object的实例对象,所以prototype中的__proto__指向Object的prototype,所以printfName也在per的原型链上,可以访问
	per.printfName();//GZH
	

4. 原型对象的指向是可以被改变的

因为prototype也是一个对象,所以他也可以指向任何对象,这也是继承的重要基础。

    function Person(name) {
      this.name = name;
    }
		Person.prototype.run = function(){
      console.log("跑步");
    }
		function Student() {
			
   	}
		Student.prototype.study = function(){
      console.log("学习");
    }

		let stu1 = new Student();
		//可以访问study,因为study在原型链上
		stu1.study();//学习

		//改变Student的指向,此时Student.prototype = {age,__proto__(指向Person的prototype)}
		Student.prorotype = new Person();

		let stu2 = new Student();
		stu2.run();//可以访问,因为run()方法在原型链上
		stu2.study();//报错,因为原型链上已经发生改变

4. 继承

1. 原型链继承

    //父类 人类 --- 姓名,年龄,性别, -行为- 吃,玩
    //子类 学生 --- 姓名,年龄,性别, -行为- 吃,玩,学习
    function Person(name, age, sex) {
      this.name = name;
      this.age = age;
      this.sex = sex || 1;
    }
    Person.prototype.eat = function () {
      console.log(this.name + "吃东西");
    }
    Person.prototype.play = function () {
      console.log(this.name + "玩")
    }

    function Student(sorce) {
      this.sorce = sorce;
    }
    Student.prototype = new Person("小明", 18, 1);
    Student.prototype.study = function () {
      console.log(this.name + "在学习");
    }
    let stu = new Student("100");
    console.dir(stu);
    stu.study();

优点:优点是继承了父类的模板,又继承了父类的原型对象

缺点:对象属性固定,修改起来麻烦;例如连续创建几个学生实例对象,名字都是相同,要改则需要用stu.name = “…”,这样修改。

2. 借用构造函数修改


function Person(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
}
Person.prototype.eat = function () {
  console.log("吃饭饭");
};

function Student(name, age, sex, score) {
  Person.call(this, name, age, sex);//利用call的方法执行Person构造函数,初始化属性
  this.score = score;
}

let stu = new Student("小明", 18, "男", 100);
stu.eat();

优点:比原型链继承更加灵活,实例对象时即可初始化自身属性

缺点:无法访问父类的prototype方法

3.组合继承

// 借用组合继承(原型继承+借用构造函数继承)
function Person(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
}
Person.prototype.eat = function () {
  console.log("吃饭饭");
};

function Student(name, age, sex, score) {
  Person.call(this, name, age, sex);
  this.score = score;
}
Student.prototype = new Person();//改变原型指向
Student.prototype.constructor = Student;//将构造函数指向本身

let stu = new Student("小明", 18, "男", 100);
stu.eat();

优点:结合原型链继承和借用构造函数继承,就完美地解决了之前这二者各自表现出来的缺点。即可继承属性,又可继承方法

缺点:Person()方法执行了两次,一次 Person.call(this, name, age, sex);一次new Person();

4.拷贝继承

//1.赋值,无开辟新空间
let obj = {
  name:"Gzh",
  age:18
}
let obj1 = obj;


//2.开辟了新空间
function Person() {

}
Person.prototype.name = "阿萨德";
Person.prototype.age = 18;
Person.prototype.sex = "男";
Person.prototype.sayHi = function () {
  console.log("hello");
};
let student = {};
for (let key in Person.prototype) {
  student[key] = Person.prototype[key];
}

优点:支持多继承

缺点:1、效率较低,内存占用高(因为要拷贝父类的属性)

​ 2、无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

5. 寄生组合继承

    function Person(name, age, sex) {
      this.name = name;
      this.age = age;
      this.sex = sex;
    }
    Person.prototype.eat = function () {
      console.log(this.name + "在吃饭");
    }

    function Student(name, age, sex, grade) {
      Person.call(this, name, age, sex);
      this.grade = grade;
    }
    (function () {
      function Super() {};
      Super.prototype = Person.prototype;
      console.log(Super);
      Student.prototype = new Super();
    })();
    let stu = new Student("wassupbro", 18, "男", "大二");
    stu.eat();//wassupbro在吃饭
    console.dir(stu);

优点:既可以继承属性,又可以继承方法,也只执行了一次Person的构造函数,堪称完美

缺点:实现起来稍复杂

5.call、apply、bind方法

1. js中this指向问题

1. 在全局环境中

在全局执行环境中(在任何函数体外部),this都是指向全局对象。在浏览器中,window对象即是全局对象;

console.log(this); //Window
var a = 1;
console.log(window.a);  //1
2.在函数环境中

在函数内容,this指向取决于函数调用的方式;

function f(){
"use strict"; //使用严格模式
console.log(this); } f(); // window ;使用严格模式时,输出undefined
3. 作为对象的方法调用时

当函数作为对象的方法被调用时,this指向调用的该函数的对象;

var obj = {
  a: 37,
  fn: function() {
    return this.a;
  }
};
console.log(obj.fn());  // 37
4.作为构造函数

当一个函数用作构造函数时(使用new关键字),它的this被绑定到正在构造的新对象。

虽然构造器返回的默认值是this所指的那个对象,但它仍可以手动返回其他的对象(如果返回值不是一个对象,则返回this对象)。

function C(){
  this.a = 37;
}

var o = new C();
console.log(o.a); //  37
function C2(){
  this.a = 37;
  return {a:38};
}
o = new C2();
console.log(o.a); //  38,手动设置了返回对象
5.作为DOM事件处理函数

当函数被用作事件处理函数时,它的this指向触发事件的元素(一些浏览器在使用非addEventListener的函数动态添加监听函数时不遵守这个约定)。

2. call和apply方法

  • 作用:用来改变函数中this的指向
  • 用法:Person.call(对象,参数1,参数2,…);Person.apply(对象,[参数1,参数2,…])
  • 使用:需要调用其他对象的方法,并且this指向自身时;
  • 实例,下面的例子↓
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.eat = function () {
  console.log(this.name + "在吃饭");
};
let obj = {
  name: "gzh",
  age: 18
};
let per = new Person("htat", 18);
per.eat(); //htat在吃饭

per.eat.call(obj); //gzh在吃饭,改变了this的指向

3.bind方法

  • 作用:改变this指向,复制一份到变量

  • 实例

    let obj = {
      name: "gzh",
      age: 18,
      eat: function () {
        console.log(this.name);
      }
    };
    let obj2 = {
      name: "http"
    };
    obj2.eat = obj.eat.bind(obj2); //改变了this的指向并将eat方法复制给obj2.eat
    obj2.eat();//http
    
    

6.闭包

  • 概念:函数A内的数据可以被函数B访问

  • 模式:函数闭包,对象模式的闭包

  • 作用:缓存数据,延长作用域;私有化变量

  • 缺点:可能会造成数据泄露

//例子:
//函数模式闭包
function f3() {
  let num = 10;
  return function () {
    return ++num;
  };
}
let f4 = f3();
console.log(f4());//11 num没有被系统回收
console.log(f4());//12
console.log(f4());//13
//对象模式闭包
function f2() {
  let num = 20;
  let obj = {
    age: num,
  };
  return obj;
}
console.log(f2().age);//函数外可以访问到f2中的变量,

利用闭包实现一个点赞的案例

	<input type="button" value="点赞(1)">
  <input type="button" value="点赞(1)">
  <input type="button" value="点赞(1)">
  <input type="button" value="点赞(1)">
  <script>
    let btnArr = document.getElementsByTagName("input");
    for (let i = 0; i < btnArr.length; i++) {
      let value = 1;
      btnArr[i].onclick = function () {
        this.value = "点赞(" + (++value) + ")";//注册点击事件,可以访问到value
      }
    }
  </script>

7. 沙箱

概念:沙箱 就是形成一个密闭的环境 不能 主动的影响外界,在js中就是自执行函数

作用:新建一块可用于模块运行的环境(沙箱),自己的代码放在回调函数里,且不会对其他的个人沙箱造成影响

let num = 1000;
((function(){
  //在自执行函数中有自己的作用域
  let num = 10;
  console.log(num);//10
})())
console.log(num);//1000

8. 浅拷贝和深拷贝

js中有基本类型(Number,String,Null,Undefined,Boolean,Symbol)和引用类型(Object,Array,Function),基本类型的赋值会复制值,而引用类型的赋值则会复制他的引用地址。

1.浅拷贝

概念:将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用

实现方法:

  1. 遍历复制

    let obj2 = {
      name: "gzh",
      age: 18,
      hobby: ["study", "basketball", "football"],
      firend: [{
        name: "asd",
        age: 18
      }]
    };
    let obj3 = {};
    for (let key in obj2) {
    	 obj3[key] = obj2[key];
    }
    obj3.name = "hha";
    obj3.hobby[0] = "run";
    console.log(obj2.name);//gzh
    console.log(obj2.hobby[0]);//run,只做第一层拷贝,obj3.hobby跟obj2.hobby指向同一个引用
    
  2. ES6的Object.assign()

    let obj4 = Object.assign();
    obj4.name = "hha";
    obj4.hobby[0] = "run";
    console.log(obj2.name);//gzh
    console.log(obj2.hobby[0]);//run,只做第一层拷贝,obj3.hobby跟obj2.hobby指向同一个引用
    
  3. ES6的扩展运算符

    let obj5 = {...obj2};
    obj5.name = "hha";
    obj5.hobby[0] = "run";
    console.log(obj2.name);//gzh
    console.log(obj2.hobby[0]);//run,只做第一层拷贝,obj3.hobby跟obj2.hobby指向同一个引用
    
2.深拷贝

概念:深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝

实现方法:

  1. JSON.parse()和JSON.stringify()

    let obj6 = JSON.parse(JSON.stringify(obj2));
    obj6.name = "hha";
    obj6.hobby[0] = "run";
    console.log(obj2.name);//gzh
    console.log(obj2.hobby[0]);//study 深拷贝,两个对象互不影响
    

    缺点:当值为undefinedfunctionsymbol 会在转换过程中被忽略。。。

  2. 自己封装函数

    function extend(a, b) {
      for (let key in a) {
        let item = a[key];
        if (item instanceof Array) {
          b[key] = [];
          extend(item, b[key]);
        } else if (item instanceof Object) {
          b[key] = {};
          extend(item, b[key]);
        } else {
          b[key] = item;
        }
      }
    }
    let obj7 = extend(obj2,obj7);
    obj7.name = "hha";
    obj7.hobby[0] = "run";
    console.log(obj2.name);//gzh
    console.log(obj2.hobby[0]);//study 深拷贝,两个对象互不影响
    
  3. immutable.js库

下次继续更新,个人博客zehongguo.github.io

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值