1 数据类型& 判断方法
- javaScript 有两种数据类型:
- 基本(值)类型
-
string: 任意字符串
-
number: 任意的数字
-
boolean: true/false
-
undefined: undefined
-
null: null
undefined 与 null 的区别: undefined是 未赋值 null是 赋值了就是null,以后可以对此变量赋值,另外,变量 = null 也是一种垃圾处理机制。
-
- 对象(引用)类型
- Object: 任意对象
- Function: 一种特别的对象(可以执行)
- Array: 一种特别的对象(数值下标, 内部数据是有序的)
- 判断
- typeof:
- 可以判断: undefined/ 数值 / 字符串 / 布尔值 / function
- 不能判断: null与object object与array
- ===
- 不可以判断: undefined, null
var a,b
a = null
console.log(typeof a,a==='null')// 'object' false
b = undefined
console.log(typeof b,a==='undefined')// undefined false
- instanceof:
- 判断对象的具体类型,用来判断 A 是否为 B 的实例
var b1 = {
b2: [1, 'abc', console.log],
b3: function () {
console.log('b3')
return function () {
return 'fangyuan'
}
}
}
console.log(b1 instanceof Object, b1 instanceof Array) // true false
console.log(b1.b2 instanceof Array, b1.b2 instanceof Object) // true true
console.log(b1.b3 instanceof Function, b1.b3 instanceof Object) // true true
console.log(typeof b1.b2, '-------') // 'object'
console.log(typeof b1.b3==='function') // true
console.log(typeof b1.b2[2]==='function')//false
console.log(b1.b3()()) //b3 fangyuan
- Object.prototype.toString
- toString是Object原型对象上的一个方法,该方法默认返回其调用者的具体类型,更严格的讲,是 toString运行时this指向的对象类型, 返回的类型格式为[object,xxx],xxx是具体的数据类型,基本上所有对象的类型都可以通过这个方法获取到。
console.log(Object.prototype.toString.call(''))//[object String]
console.log(Object.prototype.toString.call(123))//[object Number]
console.log(Object.prototype.toString.call(true)) //[object Boolean]
console.log(Object.prototype.toString.call(undefined))//[object Undefined]
console.log(Object.prototype.toString.call(null))//[object Null]
console.log(Object.prototype.toString.call(new Function()))//[object Function]
console.log(Object.prototype.toString.call([1,2,4]))//[object Array]
变量 - 内存
- 2个引用变量指向同一个对象,通过一个变量修改对象内部的数据,其它变量看到的是修改后的数据。
注意下例中obj2.age = 12是修改对象内部的数据
var obj1 = {name:'tom'}
var obj2 = obj1
obj2.age = 12
console.log(obj1.age)//12
function fn(obj){
obj.name = 'lily'
}
fn(obj1)
console.log(obj2.name)//lily
- 2个引用变量指向同一个对象,让其中一个引用变量指向另一个对象,另一个变量依然指向前一个对象
注意下例中
obj = {age:15}是引用变量指向另一个对象
var a = {age:12}
var b = a
a = {name:`sam`,age: 18}
console.log(b.age,a.name,a.age)//12 sam 18
function fn2(obj) {
obj = {age:15}
}
fn2(a)
console.log(a.age) //18
//函数也是对象
var a = 3
function fn(b) {
b = b + 1
}
fn(a)
console.log(a)//3
2 对象
对象:多个数据的封装体,一个对象代表现实中的一个事物,属性值是函数叫方法
函数 :具有特定功能的封装体,只有函数可以执行; 提高代码复用;便于阅读 函数声明
test()
obj.test()
new test()
test.call/apply(obj) //可以让一个函数称为指定任意对象的方法进行调用
var obj = {}
function test2() {
this.xxx = 'july'
}
//obj.test2() 不能直接调用,更本就没有
test2.call(obj) //可以让一个函数称为指定任意对象的方法进行调用
console.log(obj.xxx)
(1) new
1 以ctor.prototype为原型创建一个对象。
2 执行构造函数并将this绑定到新创建的对象上。
3 判断构造函数执行返回的结果是否是引用数据类型,若是则返回构造函数执行的结果,否则返回创建的对象。
function newOperator(ctor, ...args) {
if (typeof ctor !== 'function') {
throw new TypeError('Type Error');
}
const obj = Object.create(ctor.prototype);
const res = ctor.apply(obj, args);
const isObject = typeof res === 'object' && res !== null;
const isFunction = typeof res === 'function';
return isObject || isFunction ? res : obj;
}
(2) 构造函数创建对象
(1)使用工厂方法创建对象:
通过该方法可以大批量的创建对象,使用的构造函数都是Object。
所以创建的对象都是Object这个类型,就导致我们无法区分出多种不同类型的对象
(2)因此使用构造函数创建对象:
* 构造函数和普通函数的区别就是调用方式的不同
* 普通函数是直接调用,而构造函数需要使用new关键字来调用
* 使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。
* 我们将通过一个构造函数创建的对象,称为是该类的实例
-
构造函数的执行流程:
* 1.一旦出现new,立刻创建一个新的对象 * 2.将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象 * 3.逐行执行函数中的代码 * 4.将新建的对象作为返回值返回
3 this
* 解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是this,
this指向的是一个对象,这个对象我们称为函数执行的 上下文对象,
根据函数的调用方式的不同,this会指向不同的对象:
*1.以函数的形式调用时,this永远都是window
*2.以方法的形式调用时,this就是调用方法的那个对象
*3 以构造函数的形式调用时,this是新创建的对象
*4 使用call 和 apply 调用时,this是指定的那个对象
var name = '全局'
function fun() {
console.log(this.name)
}
var obj = {
name:'tom',
sayName:fun
}
var obj2 = {
name:'lily',
sayName:fun
}
//根据函数的调用方式的不同,this会指向不同的对象
//1.以函数的形式调用时,this永远都是window
fun()// 全局;
//2.以方法的形式调用时,this就是调用方法的那个对象
obj.sayName()//tom;
obj2.sayName()//lily
如何确定this的值:
test(): window
p.test(): p
new test(): 新创建的对象
p.call(obj): obj
// call()和apply() 方法
function fun() {
alert(this.name)
}
var obj = {name:'obj',
sayName:function () {
alert(this.name)
}}
var obj2 = {name:'obj2'}
// fun.call(obj) //obj
// fun.apply(obj2.name)//obj2
// fun()//window
fun.call(obj,2,3)
fun.apply(obj,[2,3])
// obj.sayName.apply(obj2)
4 原型、原型链,继承
(1) 函数的prototype属性
原型就是一个对象,和其他对象没有任何区别,可以通过构造 函数来获取原型对象。 – 构造函数. prototype
// 原型prototype
console.log(Date.prototype, typeof Date.prototype)
function Fun() {
}
console.log(Fun.prototype)//原型对象,默认指向Object空对象
//给原型对象添加方法,给实例对象用
Fun.prototype.test = function () {
console.log('test()')
}
var fun = new Fun()
fun.test()
console.log(Fun.prototype)//{ test: [Function (anonymous)] }
console.log(Date.prototype.constructor === Date)//true
console.log(Fun.prototype.constructor === Fun)//true
//显式原型(Funtion的prototype) 和隐式原型(实例对象fun的__proto__)
//1 定义构造函数
function Fn() {//内部语句:this.prototype={}
}
console.log(Fn.prototype)
// 2 创建实例对象
var fn = new Fn()//内部语句: this.__proto__ =Fn.prototype
console.log(fn.__proto__)
// 对应的构造函数的显式原型 的值 = 对象的隐式原型 的值
console.log(Fn.prototype === fn.__proto__)//true,由于他们都保存相同的地址值
// 3 给构造函数的显式原型添加方法
Fn.prototype.test = function () {
console.log('test()')
}
// 4 通过实例调用原型的方法
fn.test()
(2) 原型链(隐式原型链)
- 访问一个对象的属性时,
-
先在自身属性中查找,找到返回
-
如果没有, 再沿着__proto__这条链向上查找, 找到返回
-
如果最终没找到, 返回undefined
原型对象也是对象,所以它也有原型,当我们使用一个对象的属性或方法时,会现在自身中寻找,自身中如果有,则直接使用,如果没有则去原型对象中寻找,如果原型对象中有,则使用,如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined
-
- 作用: 查找对象的属性(方法) 简单一点:就是利用原型让一个引用类型继承另一个引用类型的属性和方法;
function MyClass() {
}
MyClass.prototype.name = '我是原型中的名字'
var mc = new MyClass()
mc.age = 12
console.log(mc.name)//我是原型中的名字
console.log('name' in mc)//true
console.log(mc.hasOwnProperty('age'))//true
console.log(mc.hasOwnProperty('hasOwnProperty'))//false
console.log(mc.__proto__.hasOwnProperty('hasOwnProperty'))//false
console.log(mc.__proto__.__proto__.hasOwnProperty('hasOwnProperty'))//true
//原型链:本质上是隐式原型链
console.log(Object.prototype.__proto__)
function Fn() {
this.test1 = function () {
console.log('test1')
}
}
Fn.prototype.test2 = function () {
console.log('test2()')
}
var fn = new Fn()
fn.test1()
fn.test2()
console.log(fn.toString())
console.log(fn.test3)//undefined
//fn.test3()//TypeError: fn.test3 is not a function
//1 函数的显式原型默认指向空的Object的实例对象(但Object不满足)
console.log(Fn.prototype instanceof Object)//true
console.log(Object.prototype instanceof Object)//false
console.log(Function.prototype instanceof Object)//true
//2 所有函数都是Function的实例(Function也是Function的实例)
console.log(Function.__proto__ === Function.prototype)
//3 Object的原型对象是原型链的尽头
console.log(Object.prototype.__proto__)//null
//练习1
function Fn(){
}
Fn.prototype.a = 'xxx'
var fn1 = new Fn()
console.log(fn1.a)//xxx
var fn2 = new Fn()
fn2.a = 'yyy'
console.log(fn1.a,fn2.a)//xxx yyy
//练习1.5 注意:读取对象的属性值会查找原型链,但设置对象的属性值时,不会查找原型链
function Person(name,age){
this.name = name
this.age = age
}
Person.prototype.setName = function(name){
this.name = name
}
var p1 = new Person('tom',12)
p1.setName('bob')
console.log(p1)//Person { name: 'bob', age: 12 }
var p2 = new Person('jack',12)
p2.setName('lily')
console.log(p2)//Person { name: 'lily', age: 12 }
console.log(p1.__proto__ === p2.__proto__)//true
//练习 2
Object.prototype.foo = 'Object'
Function.prototype.foo = 'Function'
function Animal() {
}
var cat = new Animal()
console.log(cat.foo)//Object
console.log(Animal.foo)//Function
//练习 3
var b ={x:4}
function fn2(o) {
this.x = o.x
}
fn2.prototype = {
init:function () {
return this.x
}
}
var fn3 = new fn2({x:5})
var c = fn3.init()
console.log(fn3.init.call(b))//4
console.log(c)//5
// A(实例对象)instanceof B(构造函数)
function Foo() {}
var f1 = new Foo()
console.log(f1 instanceof Foo)//true
console.log(f1 instanceof Object)//true
console.log(Object instanceof Function)//true
console.log(Object instanceof Object)//true
console.log(Function instanceof Function)//True
console.log(Function instanceof Object)//true
function Foo() {}
console.log(Object instanceof Foo)//false
function P() {
this.name = 'A'
}
P.prototype.friend = 'B'
function F() {
this.from = 'C'
}
F.prototype = new P()
const p1 = new P()
p1.friend = 'D'
const p2 = new F()
console.log(p1.friend)//D
console.log(p2.name)//A
/*测试题1 */
var A = function() {
}
A.prototype.n = 1
var b = new A()
//b 到此为止
A.prototype = {
n: 2,
m: 3
}
var c = new A()
console.log(b.n, b.m, c.n, c.m)
//答案为1,undefined,2,3。
/* 测试题2 */
var F = function(){};
Object.prototype.a = function(){
console.log('a()')
};
Function.prototype.b = function(){
console.log('b()')
};
var f = new F();
f.a() //a()
f.b()// 报错f.b is not a function
F.a()//a()
F.b()//b()
(3) 原型继承
//继承
class father {
constructor() {
this.name = 'jack'
this.age = 30
}
sayName(){
return this.name
}
static saySex(){
return 'male'
}
}
class child extends father{
constructor() {
super();
this.name = 'tony'
this.age =2
}
sayAge(){
console.log(this.age)//undefined
}
}
let cc = new child()
let ff = new father()
console.log(cc.sayName())//tony
console.log(cc.sayAge())//2
console.log(father.saySex()) //male
// console.log(cc.saySex())
// console.log(ff.saySex())
person类继承的时候,怎么实现原型链的
function Parent() {
this.name = 'parent';
}
function Child() {
Parent.call(this);
this.type = 'children';
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
5 执行上下文
(1) 变量提升
var a = 3
function fn() {
console.log(a)//undefined
var a = 4
}
fn()
console.log(b)//undefined
fn2()//可以调用,函数提升
fn3()//不可调用,变量提升
var b =3
function fn2() {
console.log('fn2()')//fn2()
}
var fn3 = function () {
console.log('fn3()')
}
(2) 全局执行上下文
console.log(a1,window.a1)
window.a2()
console.log(this)
var a1 =2
function a2() {
console.log('a2()')
}
(3) 函数执行上下文(N+1原则)
//函数执行上下文(N+1原则),是封闭的,虚拟的,存在与栈中,只有执行函数体,才有函数执行上下文,但提前准备好了;函数调用结束后就自动释放
function fn(a1) {
console.log(a1)//2
console.log(a2)//undefine
a3()//a3()
console.log(this)//window
console.log(arguments)//伪数组(2,3)
var a2 =3
function a3() {
console.log('a3()')
}
}
fn(2,3)
执行顺序
// 1 进入window全局执行上下文
var a = 10
var bar = function (x) {
var b = 5
foo(x+b)//3.进入foo函数执行上下文
}
var foo = function (y) {
var c = 5
console.log(a+c+y)
}
bar(10)//2 进入bar函数执行上下文
console.log('global'+i)//undefined
var i = 1
foo(1)
function foo(i){
if(i==4){
return
}
console.log('foo() begin'+i)//1 2 3
foo(i+1)//递归调用:在函数内部调用自己
console.log('foo() end'+i)//3 2 1
}
console.log('global'+i) //1
//test1:先变量提升,再函数提升
function a() {}
var a
console.log(typeof a)//function
//test2
if(!(b in window)){
var b = 1
}
console.log(b)//undefined
//test3
var c= 1
function c(c) {
console.log(c)
}
c(2)// 报错 ,c不是函数,变量提升了
(4) 作用域(N+1 原则)与作用域链
首先来几题热热身,搞清楚全局作用域、函数作用域
var a = 123
function fun1() {
alert(a)//123; 函数作用域没有a,到全局作用域找到a
}
fun1()
var b = 123
function fun2() {
alert(b) // undefined; 所以函数作用域中 b 是undefined (2)
var b = 456 //首先,var b = 456 提前声明了 b (1)
}
fun2()
alert(b) //123;这里是全局作用域中找b (3)
var c = 123
function fun3() {
alert(c) // 123; 首先,函数作用域里,目前没有c,到全局作用域找c (1)
c = 456 // 这里c是全局的,因为没有 var 所以是 window.c ;也就是给全局的c重新赋值456 (2)
}
fun3()
alert(c)// 456; 由于 c 重新赋值了456 (3)
var d = 123
function fun4(d) {// 首先,这里有形参d,相对于函数作用域中 var d (1)
alert(d) // undefined; 函数作用域里,目前d没有赋值 (2)
d = 456 // 这里d 赋值为456 (3)
}
fun4()
alert(d)// 123; 这里是全局作用域中的d (4)
var e = 123
function fun5(e) {// 首先,这里有形参e,相对于函数作用域中 var e(1)
alert(e) // 123; 函数作用域中 var e = 123(3)
e = 456 // 这里e是函数的,给函数的e重新赋值456(4)
}
fun5(123)//然后,这里实参123,赋值 e = 123(2)
alert(e) //123;这里是全局作用域中找e(5)
作用域是静态的,(全局作用域、函数作用域、块作用域),用来隔离变量,不同作用域下同门变量不会有冲突
var a = 10,
b = 20
function fn(x) {
var a = 100
c = 300
console.log('fn()',a,b,c,x)//fn() 100 20 300 10
function bar(x) {
var a = 1000,
d = 400
console.log('bar()',a,b,c,d,x)//bar() 1000 20 300 400 100 //bar() 1000 20 300 400 200
}
bar(100)
bar(200)
}
fn(10)
//作用域链
var a = 1
function fn1() {
var b = 2
function fn2() {
var c = 3
console.log(c)//3
console.log(b)//2
console.log(a)//1
//console.log(d)// 保存,未定义
}
fn2()
}
fn1()
//test1
var x= 10
function fn() {
console.log(x)
}
function show(f) {
var x = 20
f()
}
show(fn)
var fn =function () {
console.log(fn)//[Function: fn]
}
fn()
//test2
var obj ={
fn2:function () {
console.log(fn2)//保存
console.log(this.fn2)//[Function: fn2]
}
}
obj.fn2()
6 闭包
闭包:函数嵌套,内部函数引用外部函数的数据,并执行外部函数
通俗的讲就是函数a的内部函数b,被函数a外部的一个变量引用的时候,就创建了一个闭包。
什么情况下会用到闭包吗? 最常见的是函数封装的时候,再就是在使用定时器的时候,会经常用到...
function fn1() {
var a = 2
var b = 3
function fn2() {//执行函数定义,就会产生闭包(不用调用内部函数)
console.log(a)
}
}
fn1()//执行外部函数
(1) 闭包的应用
1将函数作为另一个函数的返回值
function fn1() {
var a = 2//此时闭包就已经产生了,函数定义执行和函数执行是两回事(由于函数提升,内部函数对象已经创建了)
function fn2() {//创建闭包函数
a++
console.log(a)
}
return fn2
}
var f = fn1()
f()//3 执行函数
f()//4
f = null //闭包死亡,(包含闭包的函数对象称为垃圾对象)
2将函数作为实参传递给另一个函数调用
function showDelay(meg,time) {
setTimeout(function () {
alert(msg)//闭包
},time)
}
showDelay('july',2000)
3.定义JS模块,将所有的函数功能封装到一起
function myModule() {
var msg ='July'
//操作数据的行为
function doSomething() {
console.log('doSomething()'+msg.toUpperCase())
}
function doOtherthing() {
console.log('doOtherthing()'+msg.toLowerCase())
}
//向外暴露对象
return {
doSomething:doSomething,
doOtherthing:doOtherthing
}
}
//way2 更好,可以直接加载到window上。可以直接使用
(function (window) {
var msg = 'July'
//操作数据的行为
function doSomething() {
console.log('doSomething()'+msg.toUpperCase())
}
function doOtherthing() {
console.log('doOtherthing()'+msg.toLowerCase())
}
// //向外暴露对象
window.myModule2 = {
doSomething:doSomething,
doOtherthing:doOtherthing
}
})(window)
//test1
var name = 'the window'
var object = {
name:'my object',
getNameFunc:function () {
return function () {//无闭包
return this.name
};
}
};
console.log(object.getNameFunc()())//the window
//test2
var name2 = 'the window'
var object2 = {
name2:'my object',
getNameFunc:function () {
var that = this
return function () {//有闭包
return that.name2
}
}
};
console.log(object2.getNameFunc()())//my object
//test3
function fun(n,o) {
console.log(o)
return{
fun:function (m) {
return fun(m,n)//调用的是fun(n,o),有闭包,使用了n,传给o
}
};
}
var a = fun(0);a.fun(1);a.fun(2);a.fun(3);//undefined ,0,0,0
var b = fun(0).fun(1).fun(2).fun(3)//undefined ,0,1,2
var c = fun(0).fun(1);c.fun(2);c.fun(3)//undefined ,0,1,1
简单写一个闭包吧:
function a(){
var i=0;
function b(){
i++;
alert(i);
}
return b;
}
var c = a();
c();//?
c();//?
c();//?应聘者:应该是会依次弹出1,2,3。
原理: i是函数a中的一个变量,它的值在函数b中被改变,函数b每执行一次,i的值就在原来的基础上累加 1 。因此,函数a中的i变量会一直保存在内存中。当我们需要在模块中定义一些变量,并希望这些变量一直保存在内存中但又不会 “污染” 全局的变量时,就可以用闭包来定义这个模块。
闭包的用处: 它的最大用处有两个,一个是它可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
//创建数组元素
var num = new Array();
for(var i=0; i<4; i++){
//num[i] = 闭包;//闭包被调用了4次,就会生成4个独立的函数
//每个函数内部有自己可以访问的个性化(差异)的信息
num[i] = f1(i);
}
function f1(n){
function f2(){
alert(n);
}
return f2;
}
num[2](); //2
num[1](); //1
num[0](); //0
num[3](); //3
(2) 闭包的优缺点:
优点:
① 减少全局变量;
② 减少传递函数的参数量;
③ 封装;
缺点:
① 使用闭包会占有内存资源,过多的使用闭包会导致内存溢出等
解决方法:
简单的说就是把那些不需要的变量,但是垃圾回收又收不走的的那些赋值为null,然后让垃圾回收走;
7 事件循环机制
当javascript代码执行的时候会将不同的变量存于内存中的不同位置:堆(heap)和栈(stack)。其中,堆里存放着一些对象。而栈中则存放着一些基础类型变量以及对象的指针。
当我们调用一个方法的时候,js会生成一个与这个方法对应的执行环境(context),又叫执行上下文。 这个执行环境中存在着这个方法的私有作用域,上层作用域的指向,方法的参数,变量以及这个作用域的this对象。
而当一系列方法被依次调用的时候,因为js是单线程的,同一时间只能执行一个方法,于是一系列的方法被排队在执行栈中。
当一个脚本第一次执行的时候,js引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,然后从头开始执行。
如果当前执行的是一个方法,那么js会向执行栈中添加这个方法的执行环境,然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,js会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。
这个过程反复进行,直到执行栈中的代码全部执行完毕。
一个方法执行会向执行栈中加入这个方法的执行环境,在这个执行环境中还可以调用其他方法,甚至是自己,其结果不过是在执行栈中再添加一个执行环境。这个过程可以是无限进行下去的,除非发生了栈溢出。以上的过程说的都是同步代码的执行。
js引擎遇到一个异步事件后,js会将这个事件加入事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈执行完毕,它会立刻先处理微任务队列中的事件,然后再去宏任务队列中取出一个事件。
主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。
2.macro task与micro task
不同的异步任务被分为两类:先: 微任务(micro task):new Promise() 和宏任务(macro task): setInterval() ,setTimeout()
以下事件属于微任务
new Promise()
new MutaionObserver()
]
setTimeout(function () {
console.log(1);
});
new Promise(function(resolve,reject){
console.log(2)
resolve(3)
}).then(function(val){
console.log(val);
})
结果为:
2
3
1