除了 == null 之外,其他一律用 ===
const obj = { x: 100 } if (obj.a == null) {} // 相当于 if (obj.a === null || obj.a === undefined) {}
const obj1 = { x: 100 } const obj2 = obj1 let x1 = obj1.x obj2.x = 101 x1 = 102 > obj1.x // 101
!!a === true
的变量!!a === false
的变量class Student { constructor(name, number) { this.name = name this.number = number } sayHi() { console.log(`name=${this.name} and number=${this.number}`) } } // 通过类 new 对象/实例 const charlotte = new Student('夏洛特', 1001) > charlotte.name // 夏洛特 > charlotte.number // 1001
/** * 继承 **/ class People { constructor(name) { this.name = name } eat() { console.log(`${this.name} eat something`) } } class Student extends People { constructor(name, number) { super(name) this.number = number } sayHi() { console.log(`name=${this.name} and number=${this.number}`) } } class Teacher extends People { constructor(name, major) { super(name) this.major = major } teach() { console.log(`${this.name} teach ${this.major}`) } } const teacherShao = new Teacher('邵老师', 'computer') > teacherShao.teach()
原型关系:隐式原型指向显式原型的 prototype charlotte.__proto__ === Student.prototype
// jQuery class jQuery { constructor(selector) { const result = document.querySelectorAll(selector) for (let i = 0; i < result.length; i++) { this[i] = result[i] } this.length = resule.length } get(idx) { return this[idx] } map(fn) { for (let i = 0; i < this.length; i++) { const elem = this[i] fn(elem) } } }
在函数执行的地方决定,而不是在定义的时候决定
Function.prototype.myBind = function () { // 将参数拆解为数组 const args = Array.prototype.slice.call(arguments) // 获取 this(数组第一项) const t = args.shift() // fn1.bind(...) 中的 fn1 const self = this // 返回一个函数 return function () { return self.apply(t, args) } }
在函数中的变量只能在函数内访问,自己来维护该变量。如果外部需要访问可以创建一些 api 。
作用域即变量的合法使用范围,分为全局作用域、函数作用域和块级作用域(ES6 新增)
闭包:访问一个变量时,是在函数定义的地方,向上级作用域查找,不是在执行的地方
let a, i for (i = 0; i < 10; i++) { a = document.createElement('a') a.innerHTML = i + '<br>' a.addEventListener('click', function (e) { e.preventDefault() console.log(i) }) document.body.appendChild(a) }
因为 i 的声明是在 for 之外,这里是全局作用域。当点击事件发生的时候,i 的值以及变成了 10,因为循环已经结束,所以在访问 i 的时候永远都是显示 10;
let a for (let i = 0; i < 10; i++) { ... }
这里 let 声明的变量都是块级作用域,因此会在每次循环中都声明一个新的 i 变量,也就不会导致刚才的问题。
const url = '' function loadImage(src) { return new Promise((resolve, reject) => { const img = document.createElement('img') img.onload = () => { resolve(img) } img.onerror = () => { reject(new Error('图片加载失败 ${src}')) } img.src = src }) } loadImage(url).then(img => { console.log(`${img.width}x${img.height}`) }).catch(err => { console.log(err) })
JS 是单线程运行的,异步要基于回调来实现,event loop 就是异步回调实现的原理。
如果在函数最外层声明变量,他们都可以在全局被访问到;
let 和 const 是块级作用域,在 let 和 const 中声明的变量仅可在该块中使用。比如 if while for 。
var 只有函数和全局作用域,不存在块级作用域
var 变量可以重新声明和修改,并且不会出错;
let 变量可以被修改,但在作用域内不能被重新声明;
const 常量不能被修改且不能被重新声明;
var 声明的变量会被提升到作用域的顶部,并使用 undefined 对其初始化
let 声明的变量会被提升到作用域的顶部,不会对值进行初始化。所以如果尝试在声明前访问 let 变量会出错
const 声明的常量也会被提升到作用域顶部,但是必须要初始化
基本数据类型:number、bigint、string、boolean、null、undefined、symbol
引用数据类型:object
typeof:识别所有基本类型,判断是否为引用类型,识别函数
instanceof:判断两边对象是否属于实例关系
JavaScript 是基于原型的,我们创建的每个函数都有一个 prototype(原型)
属性,这个属性是一个指针,指向一个对象(原型对象),这个对象存放可以让所有实例共享的属性和方法。
原型对象默认拥有一个 constructor
属性,指向指向它的那个构造函数
每个对象都拥有一个隐藏的属性 __proto__
,指向它的原型对象
实例自身属性会屏蔽原型上面的同名属性,实例上没有的属性回去原型上面找
在已经创建了实例的情况下重写原型,会切断现有实例与新原型之间的联系
重写原型对象,会导致原型对象的 constructor
属性指向 Object
,原型链关系混乱。所以我们应该在重写原型对象的时候指定 constructor
People.prototype = { constructor: People, // ... }
JavaScript 中所有的对象都是由它的原型对象继承而来的。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链
所有原型链的终点都是 Object
函数的 prototype
属性
Object.prototype
指向的原型对象同样拥有原型,不过它的原型是 null
,而 null
没有原型
浅拷贝和深拷贝只针对对象和数组这样的引用数据类型。
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存;
深拷贝会创建一个新的一模一样的对象,新对象跟原对象不共享内存,修改新对象不会影响原对象
// Object.assign() // 当 object 只有一层的时候,是深拷贝 const user = { name: 'thinc', age: 23 } const user2 = Object.assign({}, user) user2.age = 3 console.log(user) // {name: 'thinc', age: 23} // Array.prototype.concat() const arr = [1, 2, { name: 'thinc' }] const arr2 = arr.concat() arr2[0] = 111 console.log(arr) // [111, 2, { name: 'thinc' }] // Array.prototype.slice() const arr = [1, 3, { name: 'Thinc' }] const arr2 = arr.slice() arr2[1] = 233 console.log(arr) // [1, 233, { name: 'Thinc' }]
补充说明:Array 的 slice 和 concat 方法不修改原数组,只是返回了一个浅复制原数组中元素的新数组。如果元素是引用类型,只会复制对象的引用(指针);如果是基本数据类型,会把具体的值拷贝过来。
JSON.stringify
将对象转换成 JSON 字符串,再用 JSON.parse
把字符串解析成对象。不能处理函数// JSON.parse(JSON.stringify()) let user1 = { age: 23, city: { now: 'Langfang', ago: 'Wenzhou' } } let user2 = JSON.parse(JSON.stringify(user1)) user2.age = 24 user2.city.now = 'Beijing' console.log(user1) // { age: 23, city: { now: 'Langfang', ago: 'Wenzhou' }} console.log(user2) // { age: 24, city: { now: 'Beijing', ago: 'Wenzhou' }}
const _ = require('lodash') const obj1 = { a: 1, b: { f: { g: 2 } } } const obj2 = _.cloneDeep(obj1) obj2.a = 111 obj2.b.f.g = 222 console.log(obj1) // { a: 1, b: { f: { g: 2 } } } console.log(obj2) // { a: 111, b: { f: { g: 222 } } }
/** * 深拷贝 * @param {Object} obj */ function deepClone(obj = {}) { if (typeof obj !== 'object' || obj == null) { return obj } // 初始化返回结果 let result if (obj instanceof Array) { result = [] } else if { result = {} } for (let key in obj) { // 保证 key 不是原型的属性 if (obj.hasOwnProperty(key)) { // 递归!!! result[key] = deepClone(obj[key]) } } return result }