JavaScript笔记(Update)

372 阅读13分钟

数据类型

image-20220215002905263

**null表示"没有对象",即该处不应该有值。**典型用法是:

(1) 作为函数的参数,表示该函数的参数不是对象。

(2) 作为对象原型链的终点。

Object.getPrototypeOf(Object.prototype)
// null

**undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。**典型用法是:

(1)变量被声明了,但没有赋值时,就等于undefined。

(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。

(3)对象没有赋值的属性,该属性的值为undefined。

(4)函数没有返回值时,默认返回undefined。

var i;
i // undefined

function f(x){console.log(x)}
f() // undefined

var  o = new Object();
o.p // undefined

var x = f();
x // undefined

image-20220215004056358

image-20220215010655171

image-20220215011050357

image-20220215011627588

个人心得:内存里面有堆和栈,变量放在栈中,引用类型的数据放在堆内存之中,

image-20220217161118794

image-20220217161511763

函数传值

问题:js调用函数是传递的是?

fn(a)括号里面的a会赋值给fn{}作用域里面的a相当于 var a=a

var a = 3;
function fn(a) {undefined
a = a + 1;
console.log(a);//4
}
fn(a);
console.log(a);//3
//执行到:fn(a);先读到a是3,把a的值传给形参a,形参a去加1,输出为4。实参a还是实参a,所以最后输出a仍然是3。这里是基本值传递。

function fn2(obj) {undefined
console.log(obj.name);//tom
}
var obj = {name:‘tom’};
fn2(obj);
//执行到:fn2(obj);把obj中的内容(不是把{name:‘tom’}传递给形参obj,是把obj的地址值传给形参obj,只是地址值中是 {name:‘tom’})传递给形参obj。这里是地址值传递。

image-20220217162050099

var obj={}
    function jjj(){
      console.log(11);
      console.log(this); //obj
    }
    jjj.call(obj)

image-20220217163052229

image-20220217165050543

回调函数就是你自己定义的,但是你没有自己去调用他就最终执行了(调用就是根据他的函数名字进行调用)

IIFE(立即执行函数)

image-20220217191451635

 (
      function () {
        var a = 1;
        function test() {
          console.log(a);
        }
        window.$ = function () {
          return {
            test:test
          }
        }
        console.log(window.$());
      }
    )()

this

image-20220217200633689

function Person(color){
      console.log(this);
      this.color=color;
      this.getColor=function(){
        console.log(this);
        return this.color
      }
      this.setColor=function(color){
        console.log(this);
        this.color=color
      }
    }
    //  Person('red') // window
    let p=new Person('blue'); //p
    var obj={}
    p.setColor.call(obj,"yellow")  //this就是obj,
    // call可以改变this指向,相当于obj.setColor
    // 就是在一个对象里面调用不在对象里面的方法
    p.setColor.call(obj) //obj

    function fun1(){
      function fun2(){
        console.log(this);
      }
      fun2() //window
    }
    fun1() //window

原型链

image-20220217205100307

显与隐式原型都是保存地址值

image-20220217210055416

当创建对象的时候,会在里面运行这个语句

image-20220217210532940

Object对象

image-20220218140407116

 // 用构造函数
    console.log(Object); //object在这里已经存在了
    console.log(Object.prototype);
    function fn(name){
      this.name=name,
      this.test1=function(){
        console.log(this.name)
      }
    }

    fn.prototype.test2=function(){
      console.log("test2");
    }

    let p=new fn('lqc')
    console.log(fn.prototype);
    p.test1()
    p.test2()
    console.log(fn.prototype===p.__proto__);

image-20220218140240933

个人心得:不管是自己创建多少个object类型或者是function型,他们的最终都会找到他们的原型对象.例如:在创建构造函数的时候,传入的this.prototype={}这个Object字面量对象,那么他的隐式原型就会找会object的原型对象了,可以更好理解下面0*345的地址

image-20220218140407116

原型链图片1:这个是实例的图片

原型链1

原型链2

  • 函数的显示原型都是Object空实例,是在创建构造函数的时候创建的Object空实例,他们都指向Object.prototype
  • var Foo=new Function()
  • Function=new Function()
  • 所有函数的隐式原型就是Function.prototype
  • 需要注意实例与函数的原型路线

image-20220221104237826

image-20220221105518583

instanceof原理

//假设instanceof运算符左边是L,右边是R.L是一个实例
L instanceof R 
//instanceof运算时,通过判断L的原型链上是否存在R.prototype
L.__proto__.__proto__ ..... === R.prototype//如果存在返回true 否则返回false

image-20220221123820560

原型链面试题

image-20220221125633060

答案是:1,undfind,2,3

因为创建实例的时候,this.__ proto __=构造函数的显示原型,但是后面的显示原型地址改变了

image-20220221163318104

答案:a() 未定义, a() ,b()

变量提升

image-20220221163653810

image-20220221163833038

fn3是一个变量名,不是函数名.只会提升变量,不会提升函数

执行上下文

全局执行上下文

image-20220221170436920

函数执行上下文

image-20220221170426431

image-20220221201130773

a,bar,foo是全局执行上下文,遇到函数调用就压栈,等到函数执行完就出栈,全局执行只有一个,公式就是n+1

面试题1:

image-20220221204820165

答案:这个需要注意,给js的传值基本数据类型传递的是值,而不是引用数据类.每个函数里面的作用域是不一样的

image-20220221205636170

面试题2:

function fn(){
    }
 var fn;
 console.log(fn);//function fn()

考察变量和函数提升谁先提升.答案是变量的优先级更高,而且变量被覆盖了

面试题3:

if (!(b in window)){
    var b=1
  }
  console.log(b);//undefined

面试题4:(易错)

var c = 1;
function c(c) {
    console.log(c);
}
c(2) //报错

ps:变量提升要看上面有没有输出语句或者函数等等,只要提升了

作用域与 作用域链

image-20220221221106539

es5只有全局作用域还有函数作用域,es6多了块级作用域

image-20220221221256352

面试题1:

 var x = 10;
    function fn() {
      console.log(x);
    }
    function show(f) {
      var x = 20;
      f();
    }
    show(fn) //10

var f=fn是赋值地址,所以fn()的位置是不变的,不会受到f()的影响

Object创建对象的方式

方式一:

let p=new Object()
   p.name='Tom'
   p.age=12
   p.setName=function(name){
     this.name=name
   }

方式二:字面量

image-20220221225621093

方式三:工厂函数

function createPerson(name,age){
      var obj={
        name:name,
        age:age,
        setName:function(name){
          this.name=name
        }
      }
        return obj
    }
    var p=createPerson('lqc',21)
    console.log(p);

定时器

image-20220223143049999

如果是多线程的,两个线程中,对同一个节点p,一个线程对节点P进行删除,另一个对节点p进行文本更新,那么会导致报错!

 //  var start=Date.now()
    //   setTimeout(()=>{
    //     console.log(Date.now()-start);

    //   },1000)
    // 定时器的回调函数执行要等到js主线程执行完

    setTimeout(() => {
      console.log("1");
      // alert()
    }, 1000)
    setTimeout(() => {
      console.log("2");
    }, 2000)
    setTimeout(() => {
      console.log("3");
    }, 0)
    function fn(){
      console.log('fn');
    }
    fn()

    console.log('之前');
    alert('alert') //alert会暂停主线程
    console.log('之后');
    // settimout的回调要到主线程执行

要知道异步执行原理,就先要了解同步执行。因为计算机程序执行分为同步执行和异步执行。

所谓的同步执行,就是正常的计算机执行的顺序流程:

  1.顺序控制语句  从上至下  从左至右

  2.分支控制语句  if  switch

  3.循环控制语句  for  while  do...while  for...in  forEach()

所谓的异步执行(回调函数),是一种特殊的程序的执行方式:

  1.setInterval  setTimeout

  2.事件的绑定  onclick...

  3.ajax请求

所谓异步程序的执行:

  1,所有的异步程序的执行,都会在同步程序执行结束之后,再来执行。

  2,异步程序的执行顺序,如果时间相同,看代码的先后顺序,如果时间不同,时间短的,先执行。

image-20220223145234548

js分为同步任务和异步任务,异步任务分为宏任务和微任务

image-20220222163859659

Object

Object.keys方法

Object.keys方法是JavaScript中用于遍历对象属性的一个方法 。它传入的参数是一个对象,返回的是一个数组,数组中包含的是该对象所有的属性名。 如:

var cat= { 
name:’mini’, 
age:2, 
color:’yellow’, 
desc:”cute” 
}
console.log(Object.keys(cat)); // ["name", "age", "color", "desc"]

这里有一道关于Object.keys的题目

输出对象中值大于2的key的数组

var data = {a: 1, b: 2, c: 3, d: 4};
Object.keys(data).filter(function(x) { return 1 ;})

期待输出:[“c”,”d”] 请问1处填什么?

正确答案:1 :data[x]>2

Object.keys是es5中新增的方法,用来获取对象自身所有的可枚举的属性名,但不包括原型中的属性,然后返回一个由属性名组成的数组。注意它同for..in一样不能保证属性按对象原来的顺序输出。 Object.getOwnPropertyNames也是es5中新增的方法,返回对象的所有自身属性的属性名(包括不可枚举的属性)组成的数组,但不会获取原型链上的属性。

Array.filter(function)对数组进行过滤返回符合条件的数组。

Object.values()方法

Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历( enumerable )属性的键值。

var obj = { foo: "bar", baz: 42 };  
Object.values(obj)  
// ["bar", 42]  

返回数组的成员顺序,属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是b、c、a。Object.values只返回对象自身的可遍历属性。

var obj = { 100: 'a', 2: 'b', 7: 'c' };  
Object.values(obj)  
// ["b", "c", "a"]  

如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。

Object.values('foo')  
// ['f', 'o', 'o']  

上面代码中,字符串会先转成一个类似数组的对象。字符串的每个字符,就是该对象的一个属性。因此,Object.values返回每个属性的键值,就是各个字符组成的一个数组。 如果参数不是对象,Object.values会先将其转为对象。由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以,Object.values会返回空数组。

Object.create()

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 语法 Object.create(proto, [propertiesObject]) 参数 proto 新创建对象的原型对象。 propertiesObject 可选。如果没有指定为 undefined,则是要添加到新创建对象的可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数。 返回值 一个新对象,带着指定的原型对象和属性。 如:

var parent = {
    x : 1,
    y : 1
}
var child = Object.create(parent,{
    z : {                           // z会成为创建对象的属性
        writable:true,
        configurable:true,
        value: "newAdd"
    }
});
console.log(child)//{z: "newAdd"}z: "newAdd"__proto__: x: 1y: 1__proto__: Object

Object.create()创建继承

function A(){
  this.a = 1;
  this.b = 2;
}
A.prototype.drive = function(){
  console.log('drivvvvvvvvvv');
}
//方式1
function B(){}
B.prototype = Object.create(new A()); //这里采用了new 一个实例
//方式2
function C(){
  A.call(this);
}
C.prototype = Object.create(A.prototype) //这里使用的是父类的原型

以上两种方式有什么区别? 1的缺点: 执行了 new,相当于运行了一遍 A ,如果在 A 里做了一些其它事情(如改变全局变量)就会有副作用。 用 A 创建的对象做原型,里面可能会有一些冗余的属性。 2模拟了 new 的执行过程

Object.hasOwnProperty()方法

判断对象自身属性中是否具有指定的属性。 obj.hasOwnProperty('name') 在某个对象是否拥有某个属性,判断的方法有很多,常用的方法就是object.hasOwnProperty('×××'),这个方法是不包括对象原型链上的方法的

var obj = {
    name:'fei'
}
    console.log(obj.hasOwnProperty('name'))//true
    console.log(obj.hasOwnProperty('toString'))//false

以上,obj对象存在的name属性的时候,调用这个方法才是返回true,我们知道其实每个对象实例的原型链上存在toString方法,在这里打印false,说明这个方法只是表明实例对象的属性,不包括原型链上的属性。

Object.getOwnPropertyNames()方法

Object.getOwnPropertyNames()方法返回对象的所有自身属性的属性名(包括不可枚举的属性)组成的数组,但不会获取原型链上的属性。

function A(a,aa) {
  this.a = a;
  this.aa = aa;
  this.getA = function() {
    return this.a;
  }
}
// 原型方法
A.prototype.aaa = function () {};

var B = new A('b', 'bb');
B.myMethodA = function() {};
// 不可枚举方法
Object.defineProperty(B, 'myMethodB', {
  enumerable: false,
  value: function() {}
});

Object.getOwnPropertyNames(B); // ["a", "aa", "getA", "myMethodA", "myMethodB"]

Object.getOwnPropertyNames和Object.keysq区别

Object.getOwnPropertyNames和Object.keys的区别,即Object.keys只适用于可枚举的属性,而Object.getOwnPropertyNames返回对象自动的全部属性名称。

'use strict';
(function(){
    if(!Object.getOwnPropertyNames){
        console.log('浏览器不支持getOwnPropertyNames');
        return;
    }

    //人类的构造函数
    var person = function(name, age, sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
        
        this.sing = function(){
            console.log('sing');
        }
    }
    //new 一个ladygaga
    var gaga = new person('ladygaga', 26, 'girl');

    //给嘎嘎发放一个不可枚举的身份证
    Object.defineProperty(gaga, 'id', {
        value : '1234567890',
        enumerable : false
    });

    //查看gaga的个人信息
    var arr = Object.getOwnPropertyNames(gaga);
    document.write(arr); //output: name,age,sex,sing,id

    document.write('</br>');
    
   //注意和getOwnPropertyNames的区别,不可枚举的id没有输出
    var arr1 = Object.keys(gaga);
    document.write(arr1); //output: name,age,sex,sing
})();

包装类

// 1.包装类和基本数据类型.基本数据类型遇到调用方法会包装成包装类,才可以调用方法
			let num=123;
			console.log(typeof num.toString());
			let str="hello lqc"
			console.log(str.split(" "))
			console.log(str.length);  //系统会自动把str变成new String(str)
		// 2. 包装类转换后,立即销毁
			str.name="op" //new String("op") 在这里创建并且添加数据,但是立马进行销毁
			console.log(str.name); //在在上一行已经销毁了,所以找不到了,显示undefined
			
			// 总结:Js中,我们不太需要使用包装类,系统会自动帮我们进行包装

Load 和 DOMContentLoaded 区别

Load 事件触发代表⻚⾯中的 DOM,CSS,JS,图⽚已经全部加载完毕。 DOMContentLoaded 事件触发代表初始的 HTML 被完全加载和解析,不需要等待 CSS, JS,图⽚加载。

Event Loop

1.定义: Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。 2. 众所周知 JS 是阻塞单线程语言,因为在最初 JS 就是为了和浏览器交互诞生的。如果 JS 是多线程的语言的话,我们在多个线程中处理 DOM 就可能会发⽣问题(一个线程中新加 节点,另一个线程中删除节点),当然可以引用读写锁解决这个问题。 JS 在执行的过程中会产生执行环境,这些执行环境会被顺序的加入到执行栈中。如果遇到异 步的代码,会被挂起并加⼊到 Task(有多种 task) 队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说 JS 中的异步 还是同步。 以上代码虽然 setTimeout 延时为 0,其实还是异步。这是因为 HTML5 标准规定这个函数 第2个参数不得少于 4 毫秒,不足会自动增加。所以 setTimeout 还是会在 script end 之后打印。 不同的任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务(microtask) 和 宏任 务(macrotask)。在 ES6 规范中,microtask 称为 jobs ,macrotask 称为 task 。

console.log('script start');
setTimeout(function() {
 console.log('setTimeout');
}, 0);
new Promise((resolve) => {
 console.log('Promise')
 resolve()
 }).then(function() {
 console.log('promise1');
}).then(function() {
 console.log('promise2');
});
console.log('script end');

// script start => Promise => script end => promise1 => promise2 => setTimeout

以上代码虽然 setTimeout 写在 Promise 之前,但是因为 Promise的then回调函数是属于微任务 setTimeout 属于宏任务,所以会有以上的打印。 微任务包括 process.nextTick , promise then, Object.observe , MutationObserver 宏任务包括 script , setTimeout , setInterval , setImmediate , I/O文件读写 , UI rendering 很多有个误区,认为微任务快于宏任务,其实是错误的。因为宏任务中包括了 script, 浏览器会先执一个宏任务,接下来有异步代码的话就先执行微任务。 所以正确的一次 Event loop 顺序是这样的

  1. 执行同步代码,这属于宏任务
  2. 执行栈为空,查询是否有微任务需要执行
  3. 执行所有微任务
  4. 必要的话渲染 UI
  5. 然后开始下一轮 Event loop,执行宏任务中的异步代码

通过上述的 Event loop 顺序可知,如果宏任务中的异步代码有大量的计算并且需要操作 DOM 的话,为了更快的界面响应,我们可以把操作 DOM 放在微任务中。

图层

一般来说,可以把普通文档流看成一个图层。特定的属性可以⽣成⼀个新的图层。不同的图 层渲染互不影响,所以对于某些频繁需要渲染的建议单独生成一个新图层,提高性能。但也 不能生成过多的图层,会引起反作用。 通过以下几个常用属性可以生成新图层

  • 3D 变换: translate3d 、 translateZ
  • will-change
  • video 、 iframe 标签
  • 通过动画实现的 opacity 动画转换
  • position: fixed