JavaScript 基础

&& 与 || 逻辑运算的运用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// && 与运算
true && alert("会调用");
false && alert("不会调用");
// 第一个值为 true 返回 第二个值
// 第一个值为false 返回第一个值
let result = 11 && 22; // result => 22
let result = 0 && 11; // result => 0
let result = 11 && 0; // result => 0
let result = 0 && NaN; // result => 0

// || 或运算
true || alert("不会调用");
false || alert("会调用");
// 第一个值为 true 直接返回 第一个值
// 第一个值为 false 直接返回第二个值

关系运算符

1
2
3
4
// 注意比较字符串型数字要转型
"1" < "5" // true
"11" < "5" // true
"1111" < +"5" // false

使用Unicode字符(十六进制)

  • js中\u十六进制编码
  • html中\#十进制编码

相等运算符

1
2
3
4
5
null == 0 // false
undefined == null // true undefined 衍生自 null
NaN == NaN // false
isNaN(NaN) // true
null === undefined // false

运算符优先级

1
2
// result => 11 && 优先级高  
let result = 11 || 22 && 33 // 加括号使之清晰 11 || (22 && 33)

time/timeEnd

1
2
3
console.time('name');
// coding
console.timeEnd('name');

delete/in

1
2
delete obj.name; // 删除对象属性
"name" in obj; // 检查对象是否含有有个属性 原型链查找

引用数据类型(保存的是地址)

1
2
3
4
let obj2 = obj1;
obj1.name = "kong";
obj2.name 也改变为=> "kong";
obj1 = null; // 不影响obj2

函数默认返回 undefined

1
2
3
4
5
// 立即执行函数
(function (a,b) {
alert("只执行一次");
console.log(a + b)
})(a,b);

枚举对象属性名 for .. in

1
2
3
for (let name in obj) {
console.log(obj[name]);
}
  • function funName(){} 形式创建的函数会声明提前和创建

this

  • 函数调用this指向window
  • 方法调用this指向调用的对象
  • 构造函数调用this指向新创建的实例
  • 以call和apply形式调用this指向指定的对象fun.call(obj)

prototype 为每个函数对象含有的属性,其指向一个显示原型对象

  • 所创建所定义的每一个函数,解析器都会向函数中添加一个属性prototype,指向显示原型对象
  • 当函数以构造函数调用时创建实例,所创建的实例对象含有一个隐含属性__proto__,该属性指向该构造函数的显示原型对象,多个以同个构造函数实例化的实例的隐含属性__proto__指向同一个原型对象
  • 由于函数也是Object的实例,所以函数显示原型对象也会有隐式属性__proto__,其指向构造函数Object()的显示原型对象也就是Obejct.prototype,就是所有函数的原型对象都是Object的实例,function Object()除外,
  • 而Object.prototype也含有隐含属性__proto__指向null
  • 原型对象访问方法:
    Person.prototype === Object.getPrototypeOf(person) === person.__proto__
  • 为避免污染全局作用域的命名空间,在原型对象中添加实例的公共方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function Person(name, age, gender) {
    this.name = name;
    this.age = age;
    this.gender = gender
    }
    // 每个Person实例共有的方法
    Person.prototype.sayName = function () {
    alert(this.name);
    }

    Person.prototype.pname = "我是原型对象中的属性";
    let person = new Person();
    console.log("pname" in person); // true 因为person实例对象中没有会去原型上查找
    console.log(person.hasOwnProperty("pname")); // false hasOwnProperty方法会查找实例对象本身有没有该属性

原型链

  • 函数原型对象也有自身的__proto__属性指向一个原型对象,最后的原型对象是Object的原型为null,原型间形成原型链,实例对象在原型链上都没有查找到方法和属性将返回undefined

Array

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let arr = [[1,2,3],[4,5,6],[7,8,9]];
console.log(arr); // 输出 1,2,3,4,5,6,7,8,9

let arr = [1,2,3];
arr.forEach((value, index, arr) => {
console.log(arr[index] === value)
})
// 返回元素且不影响原数组
console.log(arr.slice(0,2) == "1,2")
console.log(arr.slice(0) == "1,2,3")
console.log(arr.slice(0,-1) == "1,2")

// 删除元素组中元素并返回,可以插入新元素
arr.splice(0,2,'123') // 索引,数量,插入的元素

// 合并返回新数组
let result = arr1.concat(arr2, arr2, "1", "2");

// 转字符串
let str = arr.join('');

// 修改元素组反转数组
arr.reverse();

// 修改元素组排序数组 unicode
arr.sort();
arr.sort((a,b) => a - b;) // 降序 b-a

call/apply

1
2
3
4
// 指定函数this指向
fun1.call(obj1, a, b); // this === obj1
fun1.apply(obj1, [a, b]);
fun1.bind(obj)()

arguments 类数组对象

  • 对函数的实参进行封装
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function fun() {
    Array.isArray(arguments); // false
    arguments.length;
    arguments[0];

    arguments.callee === fun; // true 返回当前函数对象

    }

DOM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// document对象
document.body;
document.all;
document.documentElement; // html
document.getElementById("id");
document.getElementsByTagName("h1");
document.getElementsByName("nameKey");
document.getElementsByClassName("classname");
document.querySelector(".box1 div"); // 返回一个
document.querySelectorAll(".box1"); // 返回数组

document.createElement("li");
document.createTextNode("hahah");

// 节点对象
node.firstChild;
node.lastChild;
node.children;
node.childNodes;
node.firstElementChild;
node.parentNode;
node.previousSibling;
node.previousElementSibling;
node.nextSibling;
node.getElementsByTagName();
node.innerHTML;
node.innerText;

parentN.appendChild(node);
parentN.insertBefore(newNode, oldNode);
parentN.replaceChild(newNode, oldNode);
parentN.removeChild(node);
node.parentNode.removeChild(node);

// 推荐写法
let li = document.createElement("li");
li.innerHTML = "hahah";
ul.appendChild(li);

获取元素样式

1
2
3
4
5
// 读取内联样式
node.style.width;
// 读取元素当前显示的样式
node.currentStyle.width; // ie
window.getComputedStyle(node, null).width;

事件

事件委派

  • 为多个子元素的父元素添加事件监听,子元素事件冒泡到父元素
    1
    2
    3
    4
    5
    6
    ul.onclick = function(event) {
    event = event || window.event;
    if (event.target.className = "link") {

    }
    }

addEventListener

  • 可以绑定多个事件监听顺序调用
  • 可以在捕获阶段触发
  • 可以为任何其他支持事件的对象绑定
    1
    2
    3
    btn.addEventListener("click", function(){
    console.log(this)
    },false)

执行 new 操作

  • 创建一个空对象
  • 给构造函数的this值指向该对象,给对象设置__proto__指向构造函数对象的prototype属性值this.proto = Fun.prototype
  • 执行构造函数体(给对象添加属性/方法)

循环遍历加监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 添加属性
for (var i=0, length = btns.length; i < length; i++) {
var btn = btns[i];
btn.index = i;
btn.onclick = function () {
alert(this.index);
}
}

// 利用闭包
for (var i=0, length = btns.length; i < length; i++) {
(function (i) {
var btn = btns[i];
btn.onclick = function () {
alert(i);
}
})(i);
}

闭包 Closure

  • 嵌套的内部函数引用外部函数的变量或者函数,就产生了闭包,声明在一个函数中的函数,叫做闭包函数。而且内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后
  • 是一个包含被引用变量(函数)的对象
  • 特点
    • 让外部访问函数内部变量成为可能
    • 局部变量会常驻在内存中,延长局部变量的声明周期
    • 可以避免使用全局变量,防止全局变量污染
    • 会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1.内部函数作为外部函数返回值
function fun1 () {
var a = 1;
function fun2 () {
a++;
console.log(a)
}
return fun2;
}
var f = fun1(); // 外部函数一执行,就创建了一个闭包
f() // 2
f() // 3

// 2.将函数作为实参传递给另一个函数调用
function showDelay(msg, time) {
setTimeout(function() { //这个function是闭包,因为是嵌套的子函数,而且引用了外部函数的变量msg
alert(msg)
}, time)
}
showDelay('atguigu', 2000)

  • 生命周期
    产生:在嵌套内部函数定义执行完时就产生了(不是在调用时)
    死亡:引用闭包的对象成为垃圾对象时,如上例子f = null

  • 面试题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function fnnn(){
    var arr = [];
    for(var i = 0;i < 5;i ++){
    arr[i] = function(){
    return i;
    }
    }
    return arr;
    }
    var list = fnnn();
    for(var i = 0,len = list.length;i < len ; i ++){
    console.log(list[i]()); // 全部打印 5 因为 return 的 i 的地址的值已经变成 5
    }

内存溢出

  • 当程序运行所需要的内存超出了剩余的内存时,就抛出内存溢出的错误

内存泄漏

  • 占用的内存没有及时释放
  • 内存泄漏积累导致内存溢出
  • 常见内存泄漏:
    • 意外全局变量
    • 被遗忘的计时器或回调函数
    • 脱离DOM的引用:引用DOM后,元素被删除,引用被保留没有回收
    • 闭包,不合理的使用闭包

创建对象的方法

new Object()

1
2
3
4
5
6
7
8
let person = new Object();
person.name = name;
person.age = age;
person.gender = gender;
person.sayName = function(){
alert(this.name);
}

字面量{}

1
2
3
4
5
6
7
8
let obj = {
name: "kong",
age: 12,
sayName: function () {
console.log(this.name)
}
}

工厂方法创建对象

1
2
3
4
5
6
7
8
9
10
11
function create(name, age, gender) { 
let obj = {
name: name,
age: age,
gender: gender,
sayName: function () {
alert(this.name)
}
}
return obj;
}
  • 缺点:构造函数都是Object无法区分不同对象的具体类型(Person还是Student)

构造函数创建对象

  • 解决工厂方法无法区分不同对象的类型
  • person instanceof Person
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function Person(name, age, gender) {  
    this.name = name;
    this.age = age;
    this.gender = gender
    this.sayName = function () {
    alert(this.name);
    }
    }
    let person = new Person();
    // - 立即创建新对象
    // - 将构造函数的this指向新对象
    // - 执行构造函数
    // - 返回该新对象
    // 当需要生成多个实例时,方法应抽离出去,提升性能
    function sayNameFun(){
    alert(this.name);
    }
    function Person() {
    this.sayName = sayNameFun;
    }
  • 缺点:造成了不必要的函数对象的创建,因为在js中函数也是一个对象,因此如果对象属性中如果包含函数的话,那么每次都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。

原型模式

1
2
3
4
5
6
7
8
9
// let Person = function () {}
function Person() {}
Person.prototype.name = "kong";
Person.prototype.age = 12;
Person.prototype.sayName = function () {
console.log(this.name);
}
let person = new Person();
person.sayName();
  • 该模式的问题:一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如Array 这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。

组合使用构造函数模式和原型模式

1
2
3
4
5
6
7
8
9
10
11
function Person(name, age) {  
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(this.name)
}

let person = new Person();
person.sayName();

  • 缺点:因为使用了两种不同的模式,所以对于代码的封装性不够好。

寄生构造函数模式

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name, sex, age) {
const person = new Object()
person.name = name
person.sex = sex
person.age = age
person.sayName = function () {
console.log(this.name)
}
return person
}
const p1 = new Person('windy-boy', 'male', '18')

  • 缺点:无法实现对象的识别。

JS实现继承的6种方式

参考自:codermjy

  • JavaScript想实现继承的目的:重复利用另外一个对象的属性和方法。

1. 原型链继承

  • 让一个构造函数的原型是另一个类型的实例,那么这个构造函数new出来的实例就具有该实例的属性。
  • 子类型的原型为父类型的一个实例对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Parent(){
this.isShow = true
this.info = {
name: "kong",
age: 18
}
}
Parent.prototype.getInfo = function () {
console.log(this.info)
console.log(this.isShow)
}
function Child() {};
Child.prototype = new Parent(); // 重点***
Child.prototype.constructor = Child;
let child1 = new Child();
child1.info.gender = "男";
child1.getInfo(); // {name: 'kong', age: 18, gender: '男'} ture

  • 优点:写法方便简洁,容易理解。
  • 缺点:在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。

2. 借用构造函数继承

  • 在子类型构造函数的内部调用父类型构造函数;使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Parent(gender) {
this.info {
name: "kong",
age: 18,
gender: gender
}
}
function Child(gender) {
Parent.call(this, gender)
}
let child1 = new Child('男');
child1.info.nickname = 'xiaoma'
console.log(child1.info);

  • 优点:解决了原型链实现继承的不能传参的问题和父类的原型共享的问题。
  • 缺点:借用构造函数的缺点是方法都在构造函数中定义,因此无法实现函数复用。在父类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。

3. 组合继承

  • 将 原型链 和 利用构造函数 的组合到一块。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function Parent(gender) {  
    this.info = {
    name: "kong",
    age: 18,
    gender: gender
    }
    }
    Parent.prototype.getInfo = function () {
    console.log(this.info.name, this.info.age)
    }
    function Child(gender) {
    Parent.call(this, gender)
    }
    Child.prototype = new Parent()
    Child.prototype.constructor = Child // 修正constructor
    let child1 = new Child("男")
    child1.info.nickname = "xiaoming"
    child1.getInfo()
    console.log(child1.info)

  • 优点:解决了原型链继承和借用构造函数继承造成的影响。
  • 缺点:无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部

4. 原型式继承

  • Object.create() 是把现有对象的属性,挂到新建对象的原型上,新建对象为空对象,这个方法接收两个参数:作为新对象原型的对象,以及给新对象定义额外属性的对象(第二个可选)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let person = {
    name: 'mjy',
    age: 19,
    hoby: ['唱', '跳'],
    showName() {
    console.log('my name is: ', this.name)
    }
    }

    let child1 = Object.create(person)
    child1.name = 'xxt'
    child1.hoby.push('rap')
    console.log(person.hoby) // ['唱', '跳', 'rap']

  • 优点:不需要单独创建构造函数。
  • 缺点:属性中包含的引用值始终会在相关对象间共享,子类实例不能向父类传参

5. 寄生式继承

  • 寄生式继承的思路与(寄生) 原型式继承工厂模式 似, 即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function objectCopy(obj) {
function Fun() { };
Fun.prototype = obj;
return new Fun();
}

function createAnother(obj) {
let clone = objectCopy(obj);
clone.showName = function () {
console.log('my name is:', this.name);
};
return clone;
}

let person = {
name: "mjy",
age: 18,
hoby: ['唱', '跳']
}

let child1 = createAnother(person);
child1.hoby.push("rap");
console.log(child1.hoby); // ['唱', '跳', 'rap']
child1.showName(); // my name is: mjy

let child2 = createAnother(person);
console.log(child2.hoby); // ['唱', '跳', 'rap']

  • 优点:写法简单,不需要单独创建构造函数
  • 缺点:通过寄生式继承给对象添加函数会导致函数难以重用。使用寄生式继承来为对象添加函数, 会由于不能做到函数复用而降低效率;这一点与构造函数模式类似

6. 寄生组合式继承

  • 通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function objectCopy(obj) {
function Fun() { };
Fun.prototype = obj;
return new Fun();
}

function inheritPrototype(child, parent) {
let prototype = objectCopy(parent.prototype);
prototype.constructor = child;
Child.prototype = prototype;
}

function Parent(name) {
this.name = name;
this.hoby = ['唱', '跳']
}

Parent.prototype.showName = function () {
console.log('my name is:', this.name);
}

function Child(name, age) {
Parent.call(this, name);
this.age = age;
}

inheritPrototype(Child, Parent);
Child.prototype.showAge = function () {
console.log('my age is:', this.age);
}

let child1 = new Child("mjy", 18);
child1.showAge(); // 18
child1.showName(); // mjy
child1.hoby.push("rap");
console.log(child1.hoby); // ['唱', '跳', 'rap']

let child2 = new Child("yl", 18);
child2.showAge(); // 18
child2.showName(); // yl
console.log(child2.hoby); // ['唱', '跳']

  • 优点是:高效率只调用一次父构造函数,并且因此避免了在子原型上面创建不必要,多余的属性。与此同时,原型链还能保持不变;
  • 缺点是:代码复杂

浏览器JS引擎执行代码的基本流程

  • 先执行初始化代码
    • 设置定时器
    • 绑定监听
    • 发送ajax请求
  • 后面在某时刻才会执行回调函数

Web Workers多线程

  • 分线程没有window对象,不能访问DOM操作界面
  • 不能跨域加载JS
  • 利用分线程进行复杂计算
  • 不是每个浏览器都支持
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // worker.js文件 分线程 ===========
    function fibonacci(n) {
    return n < 2 ? : 1 : fibonacci(n-1) + fibonacci(n-2);
    }

    var onmessage = function (event) {
    var number = event.data;
    console.log("分线程接收到主线程发送过来的数据:" + number);
    // 计算
    var result = fibonacci(number)
    postMessage(result)
    console.log("分线程向主线程返回数据:" + result);
    }
    // =============

    // 主文件主线程 ===========
    btn.onclick = function () {
    var number = input.value;
    var worker = new Worker('worker.js');
    worker.onmessage = function (event) {
    console.log("主线程接受分线程返回的数据:" + event.data);
    alert(event.data);
    }

    worker.postMessage(number);
    console.log("主线程向分线程发送数据:" + number);
    }
    // ===============

防抖

  • 防止用户操作频繁,只执行最后一次回调
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    inp = document.querySelector("input");
    inp.oninput = debounce(function(){
    // 业务代码
    console.log(this.value)
    }, 500)

    function debounce(func, delay) {
    let timer = null;
    return function(){
    let context = this;
    let args = [...arguments];
    // 如果此时存在定时器的话,则取消之前的定时器重新记时
    if (timer) {
    clearTimeout(timer);
    timer = null;
    }
    // 设置定时器,使事件间隔指定时间后执行
    timer = setTimeout(() => {
    func.apply(context, args); // 改变this指向令其指向input
    }, delay);
    }
    }

节流

  • 用户操作频繁,控制并减少回调执行次数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    window.onscroll = throttle(function(){
    console.log(this)
    }, 500)

    // 时间戳版
    function throttle(func, delay=200) {
    let preTime = Date.now();
    return function(){
    let context = this;
    let args = [...arguments];
    let nowTime = Date.now();
    // 如果两次时间间隔超过了指定时间,则执行函数
    if (nowTime - preTime >= delay) {
    preTime = Date.now();
    return func.apply(context, args);
    }
    }
    }

    // 定时器版
    function throttle(func, wait) {
    let timer = null;
    return function() {
    let context = this;
    let args = [...arguments];
    if (!timer) {
    timer = setTimeout(()=>{
    func.apply(context, args);
    timer = null;
    }, wait)
    }
    }
    }