JavaScript 基础
&& 与 || 逻辑运算的运用
1 | // && 与运算 |
关系运算符
1 | // 注意比较字符串型数字要转型 |
使用Unicode字符(十六进制)
- js中
\u十六进制编码
- html中
\#十进制编码
相等运算符
1 | null == 0 // false |
运算符优先级
1 | // result => 11 && 优先级高 |
time/timeEnd
1 | console.time('name'); |
delete/in
1 | delete obj.name; // 删除对象属性 |
引用数据类型(保存的是地址)
1 | let obj2 = obj1; |
函数默认返回 undefined
1 | // 立即执行函数 |
枚举对象属性名 for .. in
1 | for (let name in obj) { |
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
14function 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 | let arr = [[1,2,3],[4,5,6],[7,8,9]]; |
call/apply
1 | // 指定函数this指向 |
arguments 类数组对象
- 对函数的实参进行封装
1
2
3
4
5
6
7
8
9function fun() {
Array.isArray(arguments); // false
arguments.length;
arguments[0];
arguments.callee === fun; // true 返回当前函数对象
}
DOM
1 | // document对象 |
获取元素样式
1 | // 读取内联样式 |
事件
事件委派
- 为多个子元素的父元素添加事件监听,子元素事件冒泡到父元素
1
2
3
4
5
6ul.onclick = function(event) {
event = event || window.event;
if (event.target.className = "link") {
}
}
addEventListener
- 可以绑定多个事件监听顺序调用
- 可以在捕获阶段触发
- 可以为任何其他支持事件的对象绑定
1
2
3btn.addEventListener("click", function(){
console.log(this)
},false)
执行 new 操作
- 创建一个空对象
- 给构造函数的this值指向该对象,给对象设置__proto__指向构造函数对象的prototype属性值this.proto = Fun.prototype
- 执行构造函数体(给对象添加属性/方法)
循环遍历加监听
1 | // 添加属性 |
闭包 Closure
- 嵌套的内部函数引用外部函数的变量或者函数,就产生了闭包,声明在一个函数中的函数,叫做闭包函数。而且内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后
- 是一个包含被引用变量(函数)的对象
- 特点
- 让外部访问函数内部变量成为可能
- 局部变量会常驻在内存中,延长局部变量的声明周期
- 可以避免使用全局变量,防止全局变量污染
- 会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
1 | // 1.内部函数作为外部函数返回值 |
生命周期
产生:在嵌套内部函数定义执行完时就产生了(不是在调用时)
死亡:引用闭包的对象成为垃圾对象时,如上例子f = null
面试题:
1
2
3
4
5
6
7
8
9
10
11
12
13function 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 | let person = new Object(); |
字面量{}
1 | let obj = { |
工厂方法创建对象
1 | function create(name, age, gender) { |
- 缺点:构造函数都是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
20function 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 | // let Person = function () {} |
- 该模式的问题:一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如Array 这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。
组合使用构造函数模式和原型模式
1 | function Person(name, age) { |
- 缺点:因为使用了两种不同的模式,所以对于代码的封装性不够好。
寄生构造函数模式
1 | function Person(name, sex, age) { |
- 缺点:无法实现对象的识别。
JS实现继承的6种方式
参考自:codermjy
- JavaScript想实现继承的目的:重复利用另外一个对象的属性和方法。
1. 原型链继承
- 让一个构造函数的原型是另一个类型的实例,那么这个构造函数new出来的实例就具有该实例的属性。
- 子类型的原型为父类型的一个实例对象
1 | function Parent(){ |
- 优点:写法方便简洁,容易理解。
- 缺点:在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。
2. 借用构造函数继承
- 在子类型构造函数的内部调用父类型构造函数;使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上。
1 | function Parent(gender) { |
- 优点:解决了原型链实现继承的不能传参的问题和父类的原型共享的问题。
- 缺点:借用构造函数的缺点是方法都在构造函数中定义,因此无法实现函数复用。在父类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。
3. 组合继承
- 将 原型链 和 利用构造函数 的组合到一块。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function 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
14let 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 | function objectCopy(obj) { |
- 优点:写法简单,不需要单独创建构造函数
- 缺点:通过寄生式继承给对象添加函数会导致函数难以重用。使用寄生式继承来为对象添加函数, 会由于不能做到函数复用而降低效率;这一点与构造函数模式类似
6. 寄生组合式继承
- 通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
1 | function objectCopy(obj) { |
- 优点是:高效率只调用一次父构造函数,并且因此避免了在子原型上面创建不必要,多余的属性。与此同时,原型链还能保持不变;
- 缺点是:代码复杂
浏览器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
33window.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)
}
}
}
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 CrazyKong!
评论