前端学习规划

189 阅读31分钟

HTML&CSS:

盒模型、flex布局、两/三栏布局、水平/垂直居中;

W3C标准盒子模型

标准盒子模型:元素的width或height=content的width或height;

IE盒子模型

IE盒子模型:元素的width或height=content的width或height+padding2+border2;

box-sizing 属性允许您以特定的方式定义匹配某个区域的特定元素。

box-sizing: content-box;//宽度和高度分别应用到元素的内容框。在宽度和高度之外绘制元素的内边距和边框。

box-sizing: border-box;// 为元素设定的宽度和高度决定了元素的边框盒。就是说,为元素指定的任何内边距和边框都将在已设定的宽度和高度内进行绘制。通过从已设定的宽度和高度分别减去边框和内边距才能得到内容的宽度和高度。

box-sizing: inherit;// 规定应从父元素继承 box-sizing 属性的值。

即box-sizing属性可以指定盒子模型种类,content-box指定盒子模型为W3C(标准盒模型),border-box为IE盒子模型(怪异盒模型)。

BFC(边距重叠解决方案)

其全英文拼写为 Block Formatting Context 直译为“块级格式化上下文”

BFC的原理

内部的box会在垂直方向,一个接一个的放置

每个元素的margin box的左边,与包含块border box的左边相接触(对于从做往右的格式化,否则相反)

box垂直方向的距离由margin决定,属于同一个bfc的两个相邻box的margin会发生重叠

bfc的区域不会与浮动区域的box重叠

bfc是一个页面上的独立的容器,外面的元素不会影响bfc里的元素,反过来,里面的也不会影响外面的

计算bfc高度的时候,浮动元素也会参与计算

怎么创建bfc

  • float属性不为none(脱离文档流)

  • 给父元素设置position为absolute或fixed,内部的Box会在垂直方向上一个接一个的放置。

  • display为inline-block,table-cell,table-caption,flex,inine-flex

  • overflow不为visible

应用场景

  • 自适应两栏布局

  • 清除内部浮动

  • 防止垂直margin重叠

css中的BFC详解

flex布局

注意,设为 Flex 布局以后,子元素的float、clear和vertical-align属性将失效。

flex布局教程

css实现水平垂直居中

学会flex

容器的 flex-wrap 与子项的 flex-shrink、flex-grow 之间的关系。

当 flex-wrap 为 wrap | wrap-reverse,且子项宽度和不及父容器宽度时,flex-grow 会起作用,子项会根据 flex-grow 设定的值放大(为0的项不放大);

当 flex-wrap 为 wrap | wrap-reverse,且子项宽度和超过父容器宽度时,首先一定会换行,换行后,每一行的右端都可能会有剩余空间(最后一行包含的子项可能比前几行少,所以剩余空间可能会更大),这时 flex-grow 会起作用,若当前行所有子项的 flex-grow 都为0,则剩余空间保留,若当前行存在一个子项的 flex-grow 不为0,则剩余空间会被 flex-grow 不为0的子项占据;

当 flex-wrap 为 nowrap,且子项宽度和不及父容器宽度时,flex-grow 会起作用,子项会根据 flex-grow 设定的值放大(为0的项不放大);

当 flex-wrap 为 nowrap,且子项宽度和超过父容器宽度时,flex-shrink 会起作用,子项会根据 flex-shrink 设定的值进行缩小(为0的项不缩小)。但这里有一个较为特殊情况,就是当这一行所有子项 flex-shrink 都为0时,也就是说所有的子项都不能缩小,就会出现讨厌的横向滚动条。

总结上面四点,可以看出不管在什么情况下,在同一时间,flex-shrink 和 flex-grow 只有一个能起作用,这其中的道理细想起来也很浅显:空间足够时,flex-grow 就有发挥的余地,而空间不足时,flex-shrink 就能起作用。当然,flex-wrap 的值为 wrap | wrap-reverse 时,表明可以换行,既然可以换行,一般情况下空间就总是足够的,flex-shrink 当然就不会起作用。

两栏,三栏布局

清除浮动的几种方式

1、clear:both清除浮动

2、使用after伪元素清除浮动(推荐使用)

css3动画、H5新特性。

H5和CSS3新特性

H5新特性

  1. 语义化标签:header、footer、section、nav、aside、article
  2. 增强型表单:input 的多个 type
  3. 新增表单元素:datalist、keygen、output
  4. 新增表单属性:placehoder、required、min 和 max
  5. 音频视频:audio、video
  6. canvas
  7. 地理定位
  8. 拖拽
  9. 本地存储:localStorage - 没有时间限制的数据存储;sessionStorage - 针对一个 session 的数据存储,当用户关闭浏览器窗口后,数据会被删除
  10. 新事件:onresize、ondrag、onscroll、onmousewheel、onerror、onplay、onpause
  11. WebSocket:单个 TCP 连接上进行全双工通讯的协议

cookies、sessionStorage和localStorage解释及区别

cookie、localStorage和sessionStorage 三者之间的区别以及存储、获取、删除等使用方式

localstorage sessionstorage和cookie的区别

Cookie、session和localStorage、以及sessionStorage之间的区别

JavaScript:

  • 继承、原型链、this指向、设计模式、call, apply, bind,;
  • new实现、防抖节流、let, var, const 区别、暂时性死区、event、loop;
  • promise使用及实现、promise并行执行和顺序执行;
  • async/await的优缺点;
  • 闭包、垃圾回收和内存泄漏、数组方法、数组乱序, 数组扁平化、事件委托、事件监听、事件模型

JavaScript数据类型详解

js中闭包的理解

JavaScript的数据类型

分为基本数据类型和引用数据类型,共有8种。

基本数据类型:undefined,null,Boolean,string,number,symbol(es6新增),BigInt(es10新增)

引用数据类型:object

所有引用类型的值都是object的实例。在检测一个引用类型值和object构造函数时,instanceof操作符始终会返回true。如果使用instanceof检测基本类型的值,始终会返回false,因为基本类型不是对象。

  • 基本数据类型的特点:
  1. 值是不可变的
  2. 在栈区, 基本类型值指的是简单的数据段,按值访问,可操作保存在变量中的实际的值,其占据空间小、大小固定,属于被频繁使用的数据,所以放入栈(stack)中存储。
var n = 1;
var m = true;
console.log(n == m);//true
console.log(n === m);// false复制代码

"==":只进行值的比较,会进行数据类型转换;

"===":不会转换数据类型。

  • 引用数据类型的特点:
  1. 值是可变的
  2. 放在栈内存和堆内存 引用数据类型是存放在堆(heap)中的对象,占据空间大、大小不固定,如果存放在栈中,会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
  3. 值的比较 当从一个变量向另一个变量赋引用类型的值时,同样也会将存储在变量中的对象的值复制一份到为新变量分配的空间中。
var person1 = {
	age:20
}
var person2 = person1;
person2.age = 23;
console.log(person1.age = person2.age)// true

Js中的栈内存和堆内存,内存存储(栈、堆)

JS中的基础数据类型,这些值都有固定的大小,往往都保存在栈内存中(闭包除外),由系统自动分配存储空间。我们可以直接操作保存在栈内存空间的值,因此基础数据类型都是按值访问 数据在栈内存中的存储与使用方式类似于数据结构中的堆栈数据结构,遵循后进先出的原则。

JS的引用数据类型,比如数组Array,它们值的大小是不固定的。引用数据类型的值是保存在堆内存中的对象。JS不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。这里的引用,我们可以粗浅地理解为保存在栈内存中的一个地址,该地址与堆内存的实际值相关联。


面试题:

var、let、const的区别

Const定义的值,部分能改,部分不能改。const定义的基本类型不能改变,但是定义的对象是可以通过修改对象属性等方法来改变的。


typeof的使用

typeof操作符是确定一个变量是字符串,数值,布尔值,还是undefined的最佳工具。如果变量的值是一个对象或null,则会返回"object"

var s = "Nicholas"; 
var b = true; 
var i = 22; 
var u; 
var n = null; 
var o = new Object(); 

alert(typeof s); //string 
alert(typeof i); //number 
alert(typeof b); //boolean 
alert(typeof u); //undefined 
alert(typeof n); //object 
alert(typeof o); //object
typeof null  // "object"

null值的主要作用是如果定义的变量在将来用于保存对象,那么最好将该变量初始化为null值。

使用 typeof 操作符检测函数时,该操作符会返回"function"。对正则表达式应用 typeof 会返回"function"。在 IE 和 Firefox 中,对正则表达式应用 typeof 会返回"object"。

instanceof的使用

语法 object instanceof constructor

通常想知道某个值是什么类型的对象,则用instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

所有引用类型的值都是Object的实例。确定一个值是哪种基本类型可以使用 typeof 操作符,而确定一个值是哪种引用类型可以使用 instanceof 操作符。

作用域链

内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。这些环境之间的联系是线性、有次序的。每个环境都可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个执行环境。

管理内存

JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。

到 2008 年为止,IE、Firefox、Opera、Chrome 和 Safari 的 JavaScript 实现使用的都是标记清除式的垃圾收集策略(或类似的策略),只不过垃圾收集的时间间隔互有不同。

对于全局变量,在不需要使用的时候手工解除引用,置为null。将变量设置为 null 意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存。

解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。

解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效地回收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用。

原型和原型链

JS原型与原型链深入详解

function Person(name, age) {
   this.name = name; 
    this.age = age; 
    this.sayName = function() {
        alert(this.name);      
    };
}
const person1 = new Person("gali", 18);
const person2 = new Person("pig", 20);

person1跟person2都是构造函数Person()的实例。Person.prototype指向了Person函数的原型对象,而Person.prototype.constructor又指向Person。Person的每一个实例,都含有一个内部属性__proto__,指向Person.prototype,就像上图所示,因此就有下面的关系。

console.log(Person.prototype.constructor === Person); // true
console.log(person1.__proto__ === Person.prototype); // true
console.log(person2.__proto__ === Person.prototype); // true

原型链

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例对象都包含一个指向与原型对象的指针。

深拷贝和浅拷贝

  • 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

  • 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响另一个对象。

将一个对象从内存中完成的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。

const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。

注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

浅拷贝的实现方式

  1. Object.assign(target, source)
let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' };
let obj2 = Object.assign({}, obj1);
obj2.person.name = "wade";
obj2.sports = 'football'
console.log(obj1); // { person: { name: 'wade', age: 41 }, sports: 'basketball' }
  1. 展开运算符...

展开运算符是一个 es6 / es2015特性,它提供了一种非常方便的方式来执行浅拷贝,这与 Object.assign ()的功能相同。

let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }

深拷贝的实现方式

  1. JSON.parse(JSON.stringify())

这也是利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。 这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则,因为这两者基于JSON.stringify和JSON.parse处理后,得到的正则就不再是正则(变为空对象),得到的函数就不再是函数(变为null)了。

  1. $.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝

  2. 手写递归方法 JSON.stringify 和 JSON.parse

hasOwnProperty的使用

Object的hasOwnProperty()方法返回一个布尔值,判断对象是否包含特定的自身(非继承)属性。

数组对象Array

检测数组

  1. value instanceof Array,用于Array.prototype属性是否存在于变量value的原型链上,返回一个布尔值。

实例的构造函数属性constructor指向构造函数,那么通过constructor属性也可以判断是否为一个数组。

let a = [1,3,4];
a.constructor === Array;//true

同样,这种判断也会存在多个全局环境的问题,导致的问题与instanceof相同。

存在问题:

//为body创建并添加一个iframe对象
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
//取得iframe对象的构造数组方法
xArray = window.frames[0].Array;
//通过构造函数获取一个实例
var arr = new xArray(1,2,3); 
arr instanceof Array;//false
arr.constructor === Array;//false

array属于引用型数据,在传递过程中,仅仅是引用地址的传递。

每个页面的Array原生对象所引用的地址是不一样的,在子页面声明的array,所对应的构造函数,是子页面的Array对象;父页面来进行判断,使用的Array并不等于子页面的Array;

Array.prototype !== window.frames[0].Array.prototype

  1. Array.isArray(value),确定value到底是不是数组,而不管value是在哪个全局执行环境创建的。Array.isArray() 是在ES5中提出,也就是说在ES5之前可能会存在不支持此方法的情况。

  2. 通过Object.prototype.toString.call()

let a = [1,2,3]
Object.prototype.toString.call(a) === '[object Array]';//true

它强大的地方在于不仅仅可以检验是否为数组,比如是否是一个函数,是否是数字等等

//检验是否是函数
let a = function () {};
Object.prototype.toString.call(a) === '[object Function]';//true
//检验是否是数字
let b = 1;
Object.prototype.toString.call(a) === '[object Number]';//true

对于多全局环境时, Object.prototype.toString().call()也能符合预期处理判断。

数组的转换方法

由于 alert()要接收字符串参数,所以它会在后台调用 toString()方法,由此会得到与直接调用 toString()方法相同的结果

var person1 = { 
 toLocaleString : function () { 
 return "Nikolaos"; 
 }, 
 
 toString : function() { 
 return "Nicholas"; 
 } 
}; 
var person2 = { 
 toLocaleString : function () { 
 return "Grigorios"; 
 }, 
 
 toString : function() { 
 return "Greg"; 
 } 
}; 
var people = [person1, person2]; 
alert(people); //Nicholas,Greg 
alert(people.toString()); //Nicholas,Greg 
alert(people.toLocaleString()); //Nikolaos,Grigorios

所有对象都具有 toLocaleString()、toString()和 valueOf()方法.

如果不给 join()方法传入任何值,或者给它传入 undefined,则使用逗号作为分隔符。IE7 及更早版本会错误的使用字符串"undefined"作为分隔符。

var colors = ["red", "green", "blue"];
colors.join() // "red,green,blue"

var colors = ["red", "green", "blue"];
colors.join(undefined) //"red,green,blue"

var colors = ["red", null, "blue"];
colors.valueOf() // ["red", null, "blue"]
colors.toString() // "red,,blue"

var colors = ["red", undefined, "blue"];
colors.valueOf() // ["red", undefined, "blue"]
colors.toString() // "red,,blue"

如果数组中的某一项的值是 null 或者 undefined,那么该值在 join()、toLocaleString()、toString()和 valueOf()方法返回的结果中以空字符串表示。

操作数组

  • 栈方法--LIFO(Last-In-First-Out,后进先出)
    push() 添加到数组末尾,并返回修改后数组的长度
var colors = new Array(); // 创建一个数组
var count = colors.push("red", "green");
count // 2

var item = colors.pop();
item // green

pop() 从数组末尾移除最后一项,返回移除的项

  • 队列方法--FIFO(First-In-First-Out,先进先出)。
    shift() 移除数组中的第一个项并返回该项,同时将数组长度减 1。
var colors = ["red", "222", "blue"];
var item=colors.shift();
item // "red"

unshift() 它能在数组前端添加任意个项并返回新数组的长度。

var colors = new Array(); //创建一个数组
var count = colors.unshift("red", "green");
count // 2
count = colors.unshift("black");
count // 3
var item = colors.pop()
item // "green"

数组排序

  • reverse() 反转数组项的顺序
var values = [1, 2, 3, 4, 5]; 
values.reverse(); 
alert(values); //5,4,3,2,1
  • sort()

在默认情况下,sort()方法按升序排列数组项——即最小的值位于最前面,最大的值排在最后面。 为了实现排序,sort()方法会调用每个数组项的 toString()转型方法,然后比较得到的字符串,以 确定如何排序。即使数组中的每一项都是数值,sort()方法比较的也是字符串,

var values = [0, 1, 5, 10, 15]; 
values.sort(); 
alert(values); //0,1,10,15,5

sort()方法可以接收一个比较函数作为参数,以便我们指定哪个值位于哪个值的前面。

function compare(value1, value2) { 
 if (value1 < value2) { 
 return -1; 
 } else if (value1 > value2) { 
 return 1; 
 } else { 
 return 0; 
 } 
}
var values = [0, 1, 5, 10, 15]; 
values.sort(compare);
alert(values); //0,1,5,10,15
  • concant() 不改变原数组,先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。
var colors = ["red", "green", "blue"]; 
var colors2 = colors.concat("yellow", ["black", "brown"]); 
alert(colors); //red,green,blue 
alert(colors2); //red,green,blue,yellow,black,brown
  • slice()
  1. 不改变原数组。截取数组,下标从0开始。基于当前数组中的一或多个项创建一个新数组。
  2. slice()方法可以接受一或两个参数,即要返回项的起始和结束位置。
  3. 在只有一个参数的情况下,slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项——但不包括结束位置的项。

** 如果 slice()方法的参数中有一个负数,则用数组长度加上该数来确定相应的位置。例如,在一个包含 5 项的数组上调用 slice(-2,-1)与调用 slice(3,4)得到的结果相同。如果结束位置小于起始位置,则返回空数组。**

var colors = ["red", "green", "blue", "yellow", "purple"]; 
var colors2 = colors.slice(1); 
var colors3 = colors.slice(1,4); 
alert(colors2); //green,blue,yellow,purple 
alert(colors3); //green,blue,yellow
  • splice() 主要用途是向数组的中部插入项。改变原数组,下标从0开始
  1. 删除。
var colors = ["red", "green", "blue"]; 
var removed = colors.splice(0,1); // 删除第一项
alert(colors); // green,blue 
alert(removed); // red,返回的数组中只包含一项
  1. 插入
removed = colors.splice(1, 0, "yellow", "orange"); // 从位置 1 开始插入两项
alert(colors); // green,yellow,orange,blue 
alert(removed); // 返回的是一个空数组
  1. 替换
removed = colors.splice(1, 1, "red", "purple"); // 插入两项,删除一项
alert(colors); // green,red,purple,orange,blue 
alert(removed); // yellow,返回的数组中只包含一项
  • indexOf(item,index)和 lastIndexOf(item,index)
    indexOf()方法从数组的开头(位置 0)开始向后查找,lastIndexOf()方法则从数组的末尾开始向前查找。 找到了就返回下标值,不再继续往后找。没找到返回-1。全等比较。
var numbers = [1,2,3,4,5,4,3,2,1];
numbers.indexOf(4) //3 从下标0开始查找4,返回4的下标值
numbers.indexOf(4,4) //3 从下标2开始查找4,返回4的下标值

数组的迭代方法

  • every()

判断数组中是否每个元素都满足条件

只有都满足条件才返回true;

只要有一个不满足就返回false;

  • filter() 用于对数组进行过滤。

filter()不会对空数组进行检测、不会改变原始数组

var numbers = [1,2,3,4,5,4,3,2,1]; 
var filterResult = numbers.filter(function(item, index, array){ 
 return (item > 2); 
}); 
alert(filterResult); //[3,4,5,4,3]
  • forEach() 针对每一个元素执行提供的函数。这个方法没有返回值,本质上与使用 for 循环迭代数组一样。

  • map() 创建一个新的数组,其中每一个元素由调用数组中的每一个元素执行提供的函数得来。

** forEach()方法不会返回执行结果,而是undefined。forEach()会修改原来的数组。而map()方法会得到一个新的数组并返回。**

  • some()

判断数组中是否至少有一个元素满足条件

只要有一个满足就返回true

只有都不满足时才返回false

var numbers = [1,2,3,4,5,4,3,2,1]; 
var everyResult = numbers.every(function(item, index, array){ 
 return (item > 2); 
}); 
alert(everyResult); //false 
var someResult = numbers.some(function(item, index, array){ 
 return (item > 2); 
}); 
alert(someResult); //true

Function类型

** 由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。 **

函数内部属性

在函数内部,有两个特殊的对象,arguments和this。

  • arguments,类数组对象,包含着传入函数中的所有参数
  • arguments.callee 存在函数内部,指向函数本身,该属性是一个指针,指向拥有这个 arguments 对象的函数。递归函数时使用,匿名函数递归。
  • this 全局作用域下,this指向Window。其他情况下,谁调用this就指向谁。
window.color = "red"; 
var o = { color: "blue" }; 
function sayColor(){ 
 alert(this.color); 
} 
sayColor(); //"red" 
o.sayColor = sayColor; 
o.sayColor(); //"blue"

** 函数的名字仅仅是一个包含指针的变量而已。因此,即使是在不同的环境中执行,全局的 sayColor()函数与 o.sayColor()指向的仍然是同一个函数。**

  • caller 函数对象的属性。这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为 null。为了实现更松散的耦合,也可以通过 arguments.callee.caller来访问相同的信息。

** 以上代码会导致警告框中显示 outer()函数的源代码。因为 outer()调用了 inter(),所以inner.caller 就指向 outer()。**

apply()、call()、bind()

  • 每个函数都包含两个非继承而来的方法:apply()和 call()。
  • apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例,也可以是arguments 对象。
function sum(num1, num2){ 
 return num1 + num2; 
} 
function callSum1(num1, num2){ 
 return sum.apply(this, arguments); // 传入 arguments 对象
} 
function callSum2(num1, num2){ 
 return sum.apply(this, [num1, num2]); // 传入数组
} 
alert(callSum1(10,10)); //20 
alert(callSum2(10,10)); //20
  • 在使用call()方法时,传递给函数的参数必须逐个列举出来
function sum(num1, num2){ 
 return num1 + num2; 
} 
function callSum(num1, num2){ 
 return sum.call(this, num1, num2); 
} 
alert(callSum(10,10)); //20
  • bind()。这个方法会创建一个函数的实例,其 this 值会被绑定到传给 bind()函数的值。
window.color = "red"; 
var o = { color: "blue" }; 
function sayColor(){ 
 alert(this.color); 
} 
var objectSayColor = sayColor.bind(o); 
objectSayColor(); //blue

sayColor()调用 bind()并传入对象 o,创建了 objectSayColor()函数。objectSayColor()函数的 this 值等于 o,因此即使是在全局作用域中调用这个函数,也会看到"blue"。

字符串方法 slice(),substr(),substring()

  • trim()方法。这个方法会创建一个字符串的副本,删除前置及后缀的所有空格,然后返回结果。

面向对象

数据属性

  • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。
  • [[Enumerable]]:表示能否通过 for-in 循环返回属性。
  • [[Writable]]:表示能否修改属性的值。
  • [[Value]]:包含这个属性的数据值。

访问器属性

  • [[Configurable]] : 表示能否通过delete删除属性从而重新定义属性,能否修改属性特性,能否把属性修改为数据属性。直接在对象上定义的属性,默认为true。
  • [[Enumerable]] : 表示能否通过for-in 循环访问属性。直接在对象上定义的属性,默认为true。
  • [[Get]] : 读取属性时调用的函数,默认为undefined。
  • [[Set]] : 写入属性时调用的函数,默认为undefined。

Object.defineProperty()

一旦把属性定义为不可配置的,就不能再把它变回可配置了。此时,再调用 Object.defineProperty()方法修改除 writable 之外的特性,都会导致错误:

var person = {}; 
Object.defineProperty(person, "name", { 
 configurable: false, 
 value: "Nicholas" 
}); 
//抛出错误
Object.defineProperty(person, "name", { 
 configurable: true, 
 value: "Nicholas" 
});

Object.getOwnPropertyDescriptor(obj,prop)方法,可以取得给定属性的描述符。

  • 在 JavaScript 中,可以针对任何对象——包括 DOM 和 BOM 对象,使用 Object.getOwnPropertyDescriptor()方法。
var book = {}; 
Object.defineProperties(book, { 
 _year: { 
 value: 2004 
 }, 
 edition: { 
 value: 1 
 }, 
 year: { 
 get: function(){ 
 return this._year; 
 }, 
 set: function(newValue){ 
 if (newValue > 2004) { 
 this._year = newValue; 
 this.edition += newValue - 2004; 
 } 
 } 
 } 
}); 
var descriptor = Object.getOwnPropertyDescriptor(book, "_year"); 
alert(descriptor.value); //2004 
alert(descriptor.configurable); //false
alert(typeof descriptor.get); //"undefined" 

var descriptor = Object.getOwnPropertyDescriptor(book, "year"); 
alert(descriptor.value); //undefined 
alert(descriptor.enumerable); //false 
alert(typeof descriptor.get); //"function"

创建对象

构造函数模式

1、使用new操作符创建一个构造函数的实例,发生了什么

function Person(name, age, job){ 
 this.name = name; 
 this.age = age; 
 this.job = job; 
 this.sayName = function(){ 
 alert(this.name); 
 }; 
} 
var person1 = new Person("Nicholas", 29, "Software Engineer"); 
var person2 = new Person("Greg", 27, "Doctor");

要创建 Person 的新实例,必须使用 new 操作符。以这种方式调用构造函数实际上会经历以下 4 个步骤:

(1) 创建一个新对象;

(2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);

(3) 执行构造函数中的代码(为这个新对象添加属性);

(4) 返回新对象。

在前面例子的最后,person1 和 person2 分别保存着 Person 的一个不同的实例。这两个对象都

有一个 constructor(构造函数)属性,该属性指向 Person,如下所示。

alert(person1.constructor == Person); //true 
alert(person2.constructor == Person); //true

** 当在全局作用域中调用一个函数时,this 对象总是指向 Global 对象(在浏览器中就是 window 对象)。**

原型模式

prototype(原型)属性

  • 每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
  • prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。
function Person(){ 
} 
Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function(){ 
 alert(this.name); 
}; 
var person1 = new Person(); 
person1.sayName(); //"Nicholas" 
var person2 = new Person(); 
person2.sayName(); //"Nicholas" 
alert(person1.sayName == person2.sayName); //true

但与构造函数模式不同的是,新对象的这些属性和方法是由所有实例共享的。换句话说,person1 和 person2 访问的都是同一组属性和同一个 sayName()函数。

原型对象prototype

function Kyire(name,color) {
		this.name = name;
		this.color = color;
	}
	// Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
	Kyire.prototype.type = "咔咔咔咔咔咔";
	Kyire.prototype.peach = function(){
		console.log('桃子味儿汽水');
	}

	var kate = new Kyire('das','yellow');
	var mars = new Kyire('dada','pink');

	// 实例的type属性和eat()方法,其实都是同一个内存地址,指向prototype对象,因此就提高了运行效率。
	console.log(kate.peach == mars.peach); //true

	// isPrototypeOf(),这个方法用来判断,某个proptotype对象和某个实例之间的关系。
	console.log(Kyire.prototype.isPrototypeOf(kate)); //true
  	console.log(Kyire.prototype.isPrototypeOf(mars)); //true

	// hasOwnProperty(),每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性(实例属性),还是继承自prototype对象的属性(原型属性)。
	console.log(kate.hasOwnProperty("name")); // true
  	console.log(kate.hasOwnProperty("type")); // false

	// in运算符,in运算符可以用来判断,某个实例是否含有某个属性,不论该属性存在于实例中还是原型中。
	console.log("name" in kate); // true
  	console.log("type" in kate); // true
	// in运算符还可以用来遍历某个对象的所有属性。
	for(var prop in kate) { 
		console.log("kate["+prop+"]="+kate[prop]); 
	}
    
    // Object.getPrototypeOf()返回的对象实际就是这个对象的原型
	console.log(Object.getPrototypeOf(kate) == Kyire.prototype); // true
}
  • 每当代码读取某个对象的某个属性时,搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。
  • 当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性;换句话说,添加这个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。
  • 使用 delete 操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性。
function Person(){ 
} 
Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function(){ 
 alert(this.name); 
}; 
var person1 = new Person(); 
var person2 = new Person(); 
person1.name = "Greg"; 
alert(person1.name); //"Greg"——来自实例
alert(person2.name); //"Nicholas"——来自原型
delete person1.name; 
alert(person1.name); //"Nicholas"——来自原型
  • Object.getOwnPropertyDescriptor()方法只能用于实例属性,要取得原型属性的描述符,必须直接在原型对象上调用Object.getOwnProperty.Descriptor()方法。

判断属性是否存在于原型中

// 由于 in 操作符只要通过对象能够访问到属性就返回 true,hasOwnProperty()只在属性存在于实例中时才返回 true
function hasPrototypeProperty(object, name){ 
 return !object.hasOwnProperty(name) && (name in object); 
}

** ECMAScript 5 也将 constructor 和 prototype 属性的[[Enumerable]]特性设置为 false,但并不是所有浏览器都照此实现。 **

Object.keys() 接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

function Person(){ 
} 
Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function(){ 
 alert(this.name); 
}; 
var keys = Object.keys(Person.prototype); 
alert(keys); //"name,age,job,sayName" 
var p1 = new Person(); 
p1.name = "Rob"; 
p1.age = 31; 
var p1keys = Object.keys(p1); 
alert(p1keys); //"name,age"

** 如果你想要得到所有实例属性,无论它是否可枚举,都可以使用 Object.getOwnPropertyNames()方法。**

var keys = Object.getOwnPropertyNames(Person.prototype); 
alert(keys); //"constructor,name,age,job,sayName"

** 注意结果中包含了不可枚举的 constructor 属性。Object.keys()和 Object.getOwnProperty.Names()方法都可以用来替代 for-in 循环。**


function Person(){ 
} 
Person.prototype = { 
 name : "Nicholas", 
 age : 29, 
 job: "Software Engineer", 
 sayName : function () { 
 alert(this.name); 
 } 
};

** 我们将 Person.prototype 设置为等于一个以对象字面量形式创建的新对象。最终结果相同,但有一个例外:constructor 属性不再指向 Person 了。前面曾经介绍过,每创建一个函数,就会同时创建它的 prototype 对象,这个对象也会自动获得 constructor 属性。而我们在 这里使用的语法,本质上完全重写了默认的 prototype 对象,因此 constructor 属性也就变成了新对象的 constructor 属性(指向Object 构造函数),不再指向 Person 函数。此时,尽管 instanceof操作符还能返回正确的结果,但通过 constructor 已经无法确定对象的类型了 **

var friend = new Person(); 
alert(friend instanceof Object); //true 
alert(friend instanceof Person); //true 
alert(friend.constructor == Person); //false 
alert(friend.constructor == Object); //true

** 需要改成下面这样 **

function Person(){ 
} 
Person.prototype = { 
 constructor : Person, 
 name : "Nicholas", 
 age : 29, 
 job: "Software Engineer", 
 sayName : function () { 
 alert(this.name); 
 } 
};

** 注意,以这种方式重设 constructor 属性会导致它的[[Enumerable]]特性被设置为 true。默认情况下,原生的 constructor 属性是不可枚举的 **

function Person(){ 
} 
Person.prototype = { 
 name : "Nicholas", 
 age : 29, 
 job : "Software Engineer", 
 sayName : function () { 
 alert(this.name); 
 } 
};
//重设构造函数,只适用于 ECMAScript 5 兼容的浏览器
Object.defineProperty(Person.prototype, "constructor", { 
 enumerable: false, 
 value: Person 
});

原型模式带来的问题

  • 原型模式的最大问题是由其共享的本性所导致的。
  • 原型中所有属性是被很多实例共享的,这种共享对于函数非常合适。
  • 对于包含引用类型值的属性来说,问题就比较突出了。
  • 实例一般都是要有属于自己的全部属性的。
function Person(){ 
} 
Person.prototype = { 
 constructor: Person, 
 name : "Nicholas", 
 age : 29, 
 job : "Software Engineer", 
 friends : ["Shelby", "Court"], 
 sayName : function () { 
 alert(this.name); 
 } 
}; 
var person1 = new Person(); 
var person2 = new Person(); 
person1.friends.push("Van"); 
alert(person1.friends); //"Shelby,Court,Van" 
alert(person2.friends); //"Shelby,Court,Van" 
alert(person1.friends === person2.friends); //true

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

  • 创建自定义类型的最常见方式
  • 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
function Person(name, age, job){ 
 this.name = name; 
 this.age = age; 
 this.job = job; 
 this.friends = ["Shelby", "Court"]; 
} 
Person.prototype = { 
 constructor : Person, 
 sayName : function(){ 
 alert(this.name); 
 } 
} 
var person1 = new Person("Nicholas", 29, "Software Engineer"); 
var person2 = new Person("Greg", 27, "Doctor"); 
person1.friends.push("Van"); 
alert(person1.friends); //"Shelby,Count,Van" 
alert(person2.friends); //"Shelby,Count" 
alert(person1.friends === person2.friends); //false 
alert(person1.sayName === person2.sayName); //true

** 修改了 person1.friends(向其中添加一个新字符串),并不会影响到 person2.friends,因为它们分别引用了不同的数组。**

** 实例指向原型,而不指向构造函数 **


继承相关

  • ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的

  • 其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

  • 构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。


http协议,网络相关

  • HTTP1, HTTP2, HTTPS、常见的http状态码;

  • 浏览从输入网址到回车发生了什么; 一次完整的http请求

  • 前端安全(CSRF、XSS)

  • 前端跨域、浏览器缓存、cookie, session, token, localstorage, sessionstorage;

  • TCP连接(三次握手, 四次挥手)

前端性能优化

  • 图片优化的方式
  • 500 张图片,如何实现预加载优化
  • 懒加载具体实现
  • 减少http请求的方式
  • webpack如何配置大型项目
  1. 不能用CSS通配符 *
  2. CSS选择器层叠不能超过三层
  3. CSS尽量使用类选择器
  4. 书写HTML少使用table
  5. 结构要尽量简单-DOM树要小
  6. display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发现位置变化
  7. 减少reflow对性能的影响,通过操作classname来代替一条一条的修改dom
  8. 不要把 DOM 节点的属性值放在一个循环里当成循环里的变量。不然这会导致大量地读写这个结点的属性。
  9. 尽可能的修改层级比较低的 DOM节点。当然,改变层级比较底的 DOM节点有可能会造成大面积的 reflow,但是也可能影响范围很小
  10. 为动画的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他们的 CSS 是会大大减小 reflow 。
  11. 千万不要使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。

从上面这个图上,我们可以看到,浏览器渲染过程如下:

解析HTML,生成DOM树,解析CSS,生成CSSOM树

将DOM树和CSSOM树结合,生成渲染树(Render Tree)

Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)

Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素

Display:将像素发送给GPU,展示在页面上。(这一步其实还有很多内容,比如会在GPU将多个合成层合并为同一个层,并展示在页面中。而css3硬件加速的原理则是新建合成层)

你真的了解回流和重绘吗