js高级中的一些总结

js高级中的一些总结


一.Function构造函数创建对象
所有的函数对象都是由Function构造函数创建出来的
1.创建一个无内容的函数
var fun1=new Function();
fun1();//这个函数没有实现任何功能,无意义

2.创建一个又函数体的对象
var fun2=new
Function("console.log(‘hello h5’));
fun2();//hello h5

3.创建一个带有参数体的函数对象
// 将Function中的最后一个参数作为函数体,前面的都是函数的形参。
var fun3 = new Function(“a”, “b”, “return a + b;”);
console.log(fun3(1, 2));//3

二.argunments参数详解
定义:arguments是一个对应于传递给函数的参数的类数组对象。此对象包含传递给函数的每个参数。

1.实参大于形参时:会被忽略
2形参大于实参时:没被赋值的形参回返会undefined

arguments解决形参少于实参的问题

function add(){
console.log(arguments);
}
add(1,2,3,4,5)
注意:arguments不是一个数组,而是伪数组,除了length属性和索引元素之外没有任何Array属性。


es5类的实现–class

class的基本结构
定义及用法:calss关键字定义类,创建构造函数,类名首字母大写

语法结构:
class 类名{
constructor(参数1,参数2){
//构造函数体,添加实现对象的成员
}
方法名 (){
添加原型对象
}
static 方法名{
// 添加静态成员,只能用类调用
}
// 添加原型对象成员
}

在ES6中,使用class关键字定义同样的类。

class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
doWork(){
console.log(“ES6中在原型对象上添加方法”);
}
}
var p = new Person(“Neld”,10);
console.log§;

ES6中使用class定义类只是一种语法糖(语法糖能够增加程序的可读性,从而减少程序代码出错的机会的一种语法),底层最终还是转换成ES5中使用的function类定义类,以及其中的实例成员和原型成员。

class使用的细节:

  • constructor方法是创建对象的构造方法,通常在这里为实例对象定义属性(方法也是可以的),new 之后会自动调用。
    2. constructor方法外面的方法是对象的原型方法。
    3. 在之前外面还为构造方法添加过成员(静态成员),前面要加static。

class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
static doWork(){
console.log(“ES6中在原型对象上添加方法”);
}
}
Person.doWork()

class的继承结构

语法结构:
class子类 extends 父类{
construtor(参数1,参数2);{
调用父类构造函数,将数据封装到对应属性中
super(参数1,参数2);
}
}
class实现继承的细节:

  1. Animal中定义了动物都应该有的属性和方法
  2. 使用extends关键字实现Person类继承Animal类的功能,此时他们两就属于继承关系了。
  3. 在Person的构造方法中,使用super关键字调用父类中的构造方法。

四、异常捕获(了解)

JS引擎执行JS代码时,会发生各种错误,比如:

  1. 语法相关问题(程序报错)

  2. 业务逻辑相关问题(即使出错了,也会继续执行后面的代码)

    try{
    // 可能出错的代码
    }catch(e){
    // 处理try代码块中抛出的异常
    // e 错误信息
    throw //抛出自定义异常
    }finally{
    // 无论什么情况都会执行代码块
    }:

简单示例:

try {
  	console.log(a);
 	 var b = 1;
  	console.log(b);
} catch (e) {
 	 console.log("错误信息:", e);
 	 console.log("对不起,我们正在努力的殴打程序员");
} finally {
 	 //无论什么情况都会执行
  	console.log("最终的处理");
}

需求:判断函数参数是不是数字,不是则抛出异常。

function fun(num) {
    if (typeof num !== "number") {
      throw "数据类型错误";
    }
    console.log(num);
}


try {
 	 fun("1");
} catch (e) {
    if(e==="数据类型错误"){
      	console.log('本程序猿知道了,马上就改')
    };
}

五、作用域安全的构造函数(了解)

5.1-构造函数存在的作用域安全问题

我们之前学过,构造函数除了和new创建对象之外,还可以作为普通函数直接调用。

比如:

function Person(name, age) {
    this.name = name;
    this.age = age;
}
console.log(Person("zs", 10));//undefined
console.log(new Person("ls", 12));//初始化了name和age的Person对象

直接调用该构造函数,此时会造成什么问题呢?

  1. 得不到想要的对象,这样做毫无意义。

  2. 会存在作用域安全的问题,此时this指向window,这样并不安全,有可能改了全局变量,比如:

    var name = “今天天气不错”;
    function Person(name, age) {
    this.name = name;
    this.age = age;
    }
    Person(“zs”, 10);
    console.log(name);//zs

既然存在这样的问题,我们就得解决,那么思路应该是怎样的呢?

5.2-解决构造函数作用域安全问题

方法一:判断 this == window

首先,造成上面问题的根本原因是程序员在使用的过程中可能会忘记new关键字,而导致作用域不安全的问题

所以,而当没有使用new关键字的时候,构造函数中的this关键字是指向window的

反过来,如果构造函数中的this指向window,说明没有使用new关键字,此时就有了下面的代码:

function Person(name, age) {
    if(this == window){
        throw "调用构造器需要使用new关键字";
    }else{
        this.name = name;
        this.age = age;
    }
}

但是在ES6中,这种方式存在一定的问题,此时的this不一定是指向window,原因我们后面再说。此时我们换种思路来解决。

方法二:判断构造器在不在this的原型链上

如果使用new调用该构造函数,那么this指向的是什么呢?

是当前构造函数创建的对象,所以根据类型判断也是可以的。

function Person(name, age) {
    if(!(this instanceof Person)){
        throw "调用构造器需要使用new关键字";
    }else{
        this.name = name;
        this.age = age;
    }
}

上面这种方式是完全OK的,下面我们再给出一种方式,大家可以了解一下。

方式三:使用ES6新属性target

在ES6中,为new引入了一个target属性,如果没有使用new调用构造函数,那么在该构造函数中new.target为undefined,反之为当前的构造函数。

function Person(name, age) {
	console.log(new.target);
    if(!new.target){
        throw "调用构造器需要使用new关键字";
    }else{
        this.name = name;
        this.age = age;
    }
}

六、递归

6.1-递归的基本结构(掌握)

定义:函数中用调用函数自己的结构称作递归

function f1() {
	console.log("从前有座山,山里有个庙,庙里有个老和尚给小和尚讲故事:");
	f1();
};
f1();//浏览器崩溃,因为没有结束条件——死循环

递归两个要素

1.递归的边界——找到出口,在什么情况下跳出递归

2.递归的逻辑——找到入口,什么情况下重复调用自己,调用自己做什么

var i=0;
function f1() {
	i++;
	if (i<5){// <5的时候就是入口
		f1();
	}        // =5的时候就是出口
	console.log("从前有座山,山里有个庙,庙里有个老和尚给小和尚讲故事:");
};
f1();

需求:封装一个方法,计算正整数num的阶乘(递归阶乘)

什么是阶乘(factorial):所有小于及等于该数的正整数的积

// 递归阶乘
function factorial(num) {
  if(num <= 1) { return 1 };
  return num * factorial(num - 1);
}

6.2-拷贝(掌握)

6.2.1-浅拷贝实现

需求:就是将p1对象中的属性或者是方法拷贝到p2对象中。

我们首先想到是的for…in循环,将p1的属性拷贝一份到p2中:

var p1 = {
    name:"zs",
    age:10,
    favs:["H5","Java","C"],
    wife:{
        name:"lily",
        age:8
    }
}
var p2 = {};
for(var key in p1){
    p2[key] = p1[key];
}
console.log(p2);

缺点:对象内属性是引用数据类型的话,拷贝过来的就是引用地址,并没有实现真正的拷贝,依然同一份数据。

6.2.2-深拷贝实现

所以要实现深拷贝,当我们发现属性对应的值是一个对象或数组的时候,应该将该对象或数组再拷贝一份,然后赋值给当前属性。

思路:

  1. 浅拷贝拷贝基本数据类型

  2. 判断是否引用数据类型

  3. 递归调用,完成所有层次拷贝

  4. 判断value是数组还是对象

    var p={
    name:‘小明’,
    age:13,
    favs:[‘H5’,‘Java’,‘C’],
    wife:{
    name:‘小丽’,
    age:15,
    favs:[‘H5’,‘Java’,‘C’]
    }
    };
    var p2={};
    function deepCopy(source,target) {
    for (var Key in source) {
    //只拷贝当前对象的属性
    if(source.hasOwnProperty(Key)) {
    //选择引用数据类型
    if (typeof source[Key] == ‘object’) {
    //判断value是数组还是对象
    target[Key] = isArray(source[Key]) ? [] : {};
    //递归,深层次拷贝
    deepCopy(source[Key], target[Key])
    //arguments.callee(source[Key],target[Key])
    }else{
    target[Key] = source[Key]
    }
    }
    }
    }
    function isArray(arr) {
    if(Array.isArray){
    return Array.isArray(arr)
    }else{
    return Object.prototype.toString().call(arr)==’[object Array]’
    }
    }
    deepCopy(p,p2)
    console.log(p2);

通过上面的深度拷贝得到的p2对象是和p1完全不同的两份数据,此时不再存在数据共享的问题。

6.3-排序(至少掌握一种)

6.3.1冒泡排序法(Bubble Sort)

一种计算机科学领域的较简单的排序算法。

基本思路是:重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来

需求:将数组[9,3,8,5,7]按从小到大的顺序重新排序

var arr = [9,3,8,5,7];
// 将数组中的数两两比对,调换顺序
for(var i = 0;i<arr.length-1;i++){
  for(var j = i+1;j<arr.length;j++){
    var temp;
    if(arr[i]>arr[j]){
      temp = arr[i];
      arr[i]=arr[j];
      arr[j]=temp;
    }
  }
}
console.log(arr)

6.3.2-快速排序法(Quick Sort)

快速排序是对冒泡排序的一种改进。

基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

需求:随机获取10到999之间的100个整数,并且要从大到小排序,要求使用快速排序算法。

//获取到min到max-1的total个随机数
function getRandomNum(total,min,max){
    let arr=[];
    for(let i = 0;i<total;i++){
        arr.push(Math.floor(Math.random()*(max-min)+min))
    }
    return arr
}
//快速排序算法
function arrSort(arr) {
    if (arr.length <= 1) { return arr; }
    var index = Math.floor(arr.length / 2);
    var middle = arr.splice(index, 1)[0];
    var left = [];
    var right = [];
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] < middle) {
            right.push(arr[i])
        } else {
            left.push(arr[i])
        }
    }
    return arrSort(left).concat(middle, arrSort(right))
}
console.log(arrSort(getRandomNum(100,10,1000)))

七、函数的调用和this的丢失(理解)

this指向总结

  1. 普通调用 fun() this指向调用函数的对象—window
  2. 对象调用 obj.fun() this指向调用函数的对象—obj
  3. 使用new关键字调用 new Fun() this指向函数内部创建的新对象
  4. call或者apply调用 this指向call或者apply方法的第一个参数

我们在调用函数的过程中需要时刻关注我们调用方式的不同对this的影响

如下面的案例中就发生了this的丢失问题:

<div id="main"></div>
<script>
    console.log(document.getElementById("main"));
    var getById = document.getElementById;
    console.log(getById("main"));//Uncaught TypeError: Illegal invocation
</script>

为什么会出现这样的情况:

所以,上面的代码中将根据元素id获取元素的方法赋值给了另外一个变量getById,此时的getById就等价于document.getElementById方法,所以,按理说,应该可以使用getById完成获取元素的操作,但是结果却报错了,这是为什么呢?

原因其实很简单:

  1. 在document的getElementById方法中用到了this,正常使用document调用的时候this是指向document的
  2. 要完成获取元素的功能,this就必须要指向document
  3. 当使用getById(“main”)调用函数的时候,函数中this拿到的确是window
  4. 函数中本来需要的是document拿到的确是window的时候,代码执行报错

如果非得对这段代码做优化,我们应该怎么做:

var getById = function(id){
    return document.getElementById(id);
}
console.log(getById("main"));

这段代码大家相信大家都能够看懂,所以我们以后再简化一个函数的使用的时候,一定要注意,不要轻易的将函数的this给搞丢了。

八、对象相关回顾总结(回顾)

8.1-创建对象的N中方式总结

  • 字面量
  • 内置构造函数
  • 简单工厂函数
  • 自定义构造函数
  • Object.create()
  • Object.assign()

8.2-类型检查的四种方式总结

通过前面知识点的学习,我们已经get到了四种类型检查的方式,为了在开发中灵活运用他们,我们需要对这几种方式进行总结。

  1. typeof: 主要用来判断基本类型
    console.log(typeof “abc”);//“string”
    console.log(typeof 123);//“number”
    console.log(typeof true);//“boolean”
    console.log(typeof null);//“object”
    console.log(typeof Function);//“function”
    console.log(typeof {name:“Neld”, age: 10});//“object”
    对于字符串,数字和布尔类型,返回对应类型的字符串(string, number, boolean),undefined和Function比较特殊,分别是undefined和function,这两个需要单独记忆,其他的(包括null)都是返回object

  2. constructor: 可以用来判断创建对象的构造器的类型
    function Person() {}
    function Dog() {}

    var p = new Person();
    var d = new Dog(); 
    
    console.log(p.constructor == Person);//true
    console.log(d.constructor == Dog);//true
    

    使用这种方式,我们可以知道对象的具体类型是什么。

  3. instanceof: 判断指定构造函数的原型对象是否在当前实例对象的原型链上
    function Person() {}
    function Dog() {}

    var p = new Person();
    var d = new Dog(); 
    
    console.log(p instanceof Person);//true
    console.log(d instanceof Dog);//true
    console.log(p instanceof Object);//true
    console.log(d instanceof Object);//true
    

    Person和Object的原型对象分别在p和b对象的原型链上,所以上面的返回值都是true

  4. Object.prototype.toString(): 获取数据类型对应的字符串
    console.log(Object.prototype.toString.call(“Neld”));//[object String]
    console.log(Object.prototype.toString.call(10));//[object Number]
    console.log(Object.prototype.toString.call§);//[object Object]
    console.log(Object.prototype.toString.call([]));//[object Array]
    上面几种方式都能够以自身的方式来判断数据的类型,在开发中,我们根据具体的需求选择即可。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值